Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 388cf2d

Browse files
authored
Merge pull request #100 from flutter-news-app-full-source-code/feat-custom-filters-useres-limits-configuration
Feat custom filters useres limits configuration
2 parents a7eac4b + 18aa371 commit 388cf2d

File tree

13 files changed

+317
-50
lines changed

13 files changed

+317
-50
lines changed

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
##1.0.0
2+
3+
- **BREAKING** feat!: migrated from date based versioning to semantic versioning.

‎lib/app_configuration/view/tabs/feed_configuration_tab.dart‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_decorator_form.dart';
4+
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/saved_headlines_filters_limit_form.dart';
45
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/user_preference_limits_form.dart';
56
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
67
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
@@ -67,6 +68,15 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
6768
},
6869
initiallyExpanded: expandedIndex == tileIndex,
6970
children: [
71+
Text(
72+
l10n.userContentLimitsDescription,
73+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
74+
color: Theme.of(
75+
context,
76+
).colorScheme.onSurface.withOpacity(0.7),
77+
),
78+
),
79+
const SizedBox(height: AppSpacing.lg),
7080
UserPreferenceLimitsForm(
7181
remoteConfig: widget.remoteConfig,
7282
onConfigChanged: widget.onConfigChanged,
@@ -76,11 +86,48 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
7686
},
7787
),
7888
const SizedBox(height: AppSpacing.lg),
79-
// New Top-level ExpansionTile for Feed Decorators
89+
// New Top-level ExpansionTile for User Preset Limits
8090
ValueListenableBuilder<int?>(
8191
valueListenable: _expandedTileIndex,
8292
builder: (context, expandedIndex, child) {
8393
const tileIndex = 1;
94+
return ExpansionTile(
95+
key: ValueKey('savedHeadlinesFilterLimitsTile_$expandedIndex'),
96+
title: Text(l10n.savedHeadlinesFilterLimitsTitle),
97+
childrenPadding: const EdgeInsetsDirectional.only(
98+
start: AppSpacing.lg,
99+
top: AppSpacing.md,
100+
bottom: AppSpacing.md,
101+
),
102+
expandedCrossAxisAlignment: CrossAxisAlignment.start,
103+
onExpansionChanged: (isExpanded) {
104+
_expandedTileIndex.value = isExpanded ? tileIndex : null;
105+
},
106+
initiallyExpanded: expandedIndex == tileIndex,
107+
children: [
108+
Text(
109+
l10n.savedHeadlinesFilterLimitsDescription,
110+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
111+
color: Theme.of(
112+
context,
113+
).colorScheme.onSurface.withOpacity(0.7),
114+
),
115+
),
116+
const SizedBox(height: AppSpacing.lg),
117+
SavedHeadlinesFiltersLimitForm(
118+
remoteConfig: widget.remoteConfig,
119+
onConfigChanged: widget.onConfigChanged,
120+
),
121+
],
122+
);
123+
},
124+
),
125+
const SizedBox(height: AppSpacing.lg),
126+
// New Top-level ExpansionTile for Feed Decorators
127+
ValueListenableBuilder<int?>(
128+
valueListenable: _expandedTileIndex,
129+
builder: (context, expandedIndex, child) {
130+
const tileIndex = 2;
84131
return ExpansionTile(
85132
key: ValueKey('feedDecoratorsTile_$expandedIndex'),
86133
title: Text(l10n.feedDecoratorsTitle),
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_config_form_fields.dart';
4+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
5+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_user_role_l10n.dart';
6+
import 'package:ui_kit/ui_kit.dart';
7+
8+
/// {@template saved_headlines_filters_limit_form}
9+
/// A form for configuring saved headlines filter limits within the
10+
/// [RemoteConfig].
11+
///
12+
/// This form provides fields to set the maximum number of saved filters
13+
/// for guest, authenticated, and premium users.
14+
/// {@endtemplate}
15+
class SavedHeadlinesFiltersLimitForm extends StatefulWidget {
16+
/// {@macro saved_headlines_filters_limit_form}
17+
const SavedHeadlinesFiltersLimitForm({
18+
required this.remoteConfig,
19+
required this.onConfigChanged,
20+
super.key,
21+
});
22+
23+
/// The current [RemoteConfig] object.
24+
final RemoteConfig remoteConfig;
25+
26+
/// Callback to notify parent of changes to the [RemoteConfig].
27+
final ValueChanged<RemoteConfig> onConfigChanged;
28+
29+
@override
30+
State<SavedHeadlinesFiltersLimitForm> createState() =>
31+
_SavedHeadlinesFiltersLimitFormState();
32+
}
33+
34+
class _SavedHeadlinesFiltersLimitFormState
35+
extends State<SavedHeadlinesFiltersLimitForm>
36+
with SingleTickerProviderStateMixin {
37+
late TabController _tabController;
38+
late final Map<AppUserRole, TextEditingController> _controllers;
39+
40+
@override
41+
void initState() {
42+
super.initState();
43+
_tabController = TabController(
44+
length: AppUserRole.values.length,
45+
vsync: this,
46+
);
47+
_initializeControllers();
48+
}
49+
50+
@override
51+
void didUpdateWidget(covariant SavedHeadlinesFiltersLimitForm oldWidget) {
52+
super.didUpdateWidget(oldWidget);
53+
if (widget.remoteConfig.userPreferenceConfig !=
54+
oldWidget.remoteConfig.userPreferenceConfig) {
55+
_updateControllerValues();
56+
}
57+
}
58+
59+
void _initializeControllers() {
60+
_controllers = {
61+
for (final role in AppUserRole.values)
62+
role: TextEditingController(
63+
text: _getSavedFiltersLimit(
64+
widget.remoteConfig.userPreferenceConfig,
65+
role,
66+
).toString(),
67+
)..selection = TextSelection.collapsed(
68+
offset: _getSavedFiltersLimit(
69+
widget.remoteConfig.userPreferenceConfig,
70+
role,
71+
).toString().length,
72+
),
73+
};
74+
}
75+
76+
void _updateControllerValues() {
77+
for (final role in AppUserRole.values) {
78+
final newLimit = _getSavedFiltersLimit(
79+
widget.remoteConfig.userPreferenceConfig,
80+
role,
81+
).toString();
82+
if (_controllers[role]?.text != newLimit) {
83+
_controllers[role]?.text = newLimit;
84+
_controllers[role]?.selection = TextSelection.collapsed(
85+
offset: newLimit.length,
86+
);
87+
}
88+
}
89+
}
90+
91+
@override
92+
void dispose() {
93+
_tabController.dispose();
94+
for (final controller in _controllers.values) {
95+
controller.dispose();
96+
}
97+
super.dispose();
98+
}
99+
100+
@override
101+
Widget build(BuildContext context) {
102+
final l10n = AppLocalizationsX(context).l10n;
103+
104+
return Column(
105+
crossAxisAlignment: CrossAxisAlignment.start,
106+
children: [
107+
Align(
108+
alignment: AlignmentDirectional.centerStart,
109+
child: SizedBox(
110+
height: kTextTabBarHeight,
111+
child: TabBar(
112+
controller: _tabController,
113+
tabAlignment: TabAlignment.start,
114+
isScrollable: true,
115+
tabs: AppUserRole.values
116+
.map((role) => Tab(text: role.l10n(context)))
117+
.toList(),
118+
),
119+
),
120+
),
121+
const SizedBox(height: AppSpacing.lg),
122+
SizedBox(
123+
height: 120,
124+
child: TabBarView(
125+
controller: _tabController,
126+
children: AppUserRole.values.map((role) {
127+
final config = widget.remoteConfig.userPreferenceConfig;
128+
return AppConfigIntField(
129+
label: l10n.savedHeadlinesLimitLabel,
130+
description: l10n.savedHeadlinesLimitDescription,
131+
value: _getSavedFiltersLimit(config, role),
132+
onChanged: (value) {
133+
widget.onConfigChanged(
134+
widget.remoteConfig.copyWith(
135+
userPreferenceConfig:
136+
_updateSavedFiltersLimit(config, value, role),
137+
),
138+
);
139+
},
140+
controller: _controllers[role],
141+
);
142+
}).toList(),
143+
),
144+
),
145+
],
146+
);
147+
}
148+
149+
/// Retrieves the saved filters limit for a given [AppUserRole].
150+
///
151+
/// This helper method abstracts the logic for accessing the correct limit
152+
/// from the [UserPreferenceConfig] based on the provided [role].
153+
int _getSavedFiltersLimit(UserPreferenceConfig config, AppUserRole role) {
154+
switch (role) {
155+
case AppUserRole.guestUser:
156+
return config.guestSavedFiltersLimit;
157+
case AppUserRole.standardUser:
158+
return config.authenticatedSavedFiltersLimit;
159+
case AppUserRole.premiumUser:
160+
return config.premiumSavedFiltersLimit;
161+
}
162+
}
163+
164+
/// Updates the saved filters limit for a given [AppUserRole].
165+
///
166+
/// This helper method abstracts the logic for updating the correct limit
167+
/// within the [UserPreferenceConfig] based on the provided [role] and [value].
168+
UserPreferenceConfig _updateSavedFiltersLimit(
169+
UserPreferenceConfig config,
170+
int value,
171+
AppUserRole role,
172+
) {
173+
switch (role) {
174+
case AppUserRole.guestUser:
175+
return config.copyWith(guestSavedFiltersLimit: value);
176+
case AppUserRole.standardUser:
177+
return config.copyWith(authenticatedSavedFiltersLimit: value);
178+
case AppUserRole.premiumUser:
179+
return config.copyWith(premiumSavedFiltersLimit: value);
180+
}
181+
}
182+
}

‎lib/app_configuration/widgets/user_preference_limits_form.dart‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,6 @@ class _UserPreferenceLimitsFormState extends State<UserPreferenceLimitsForm>
142142
return Column(
143143
crossAxisAlignment: CrossAxisAlignment.start,
144144
children: [
145-
Text(
146-
l10n.userContentLimitsDescription,
147-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
148-
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
149-
),
150-
),
151-
const SizedBox(height: AppSpacing.lg),
152145
Align(
153146
alignment: AlignmentDirectional.centerStart,
154147
child: SizedBox(

‎lib/l10n/app_localizations.dart‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,18 @@ abstract class AppLocalizations {
15921592
/// **'How often an ad can appear for this user role (e.g., a value of 5 means an ad could be placed after every 5 news items).'**
15931593
String get adFrequencyDescription;
15941594

1595+
/// Description for the Saved Headlines Filter Limits section
1596+
///
1597+
/// In en, this message translates to:
1598+
/// **'Set limits on the number of saved headlines filters for each user tier.'**
1599+
String get savedHeadlinesFilterLimitsDescription;
1600+
1601+
/// Title for the Saved Headlines Filter Limits section
1602+
///
1603+
/// In en, this message translates to:
1604+
/// **'Saved Headlines Filter Limits'**
1605+
String get savedHeadlinesFilterLimitsTitle;
1606+
15951607
/// Label for Ad Placement Interval
15961608
///
15971609
/// In en, this message translates to:

‎lib/l10n/app_localizations_ar.dart‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,13 @@ class AppLocalizationsAr extends AppLocalizations {
839839
String get adFrequencyDescription =>
840840
'عدد مرات ظهور الإعلان لهذا الدور المستخدم (على سبيل المثال، قيمة 5 تعني أنه يمكن وضع إعلان بعد كل 5 عناصر إخبارية).';
841841

842+
@override
843+
String get savedHeadlinesFilterLimitsDescription =>
844+
'الحد الأقصى لعدد مرشحات العناوين المحفوظة التي يمكن لهذا الدور إنشاؤها.';
845+
846+
@override
847+
String get savedHeadlinesFilterLimitsTitle => 'حدود مرشحات العناوين المحفوظة';
848+
842849
@override
843850
String get adPlacementIntervalLabel => 'فترة وضع الإعلان';
844851

‎lib/l10n/app_localizations_en.dart‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,13 @@ class AppLocalizationsEn extends AppLocalizations {
837837
String get adFrequencyDescription =>
838838
'How often an ad can appear for this user role (e.g., a value of 5 means an ad could be placed after every 5 news items).';
839839

840+
@override
841+
String get savedHeadlinesFilterLimitsDescription =>
842+
'Set limits on the number of saved headlines filters for each user tier.';
843+
844+
@override
845+
String get savedHeadlinesFilterLimitsTitle => 'Saved Headlines Filter Limits';
846+
840847
@override
841848
String get adPlacementIntervalLabel => 'Ad Placement Interval';
842849

‎lib/l10n/arb/app_ar.arb‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,14 @@
10381038
"@adFrequencyDescription": {
10391039
"description": "وصف تكرار الإعلان"
10401040
},
1041+
"savedHeadlinesFilterLimitsTitle": "حدود مرشحات العناوين المحفوظة",
1042+
"@savedHeadlinesFilterLimitsTitle": {
1043+
"description": "وصف لحد المرشحات المحفوظة"
1044+
},
1045+
"savedHeadlinesFilterLimitsDescription": "الحد الأقصى لعدد مرشحات العناوين المحفوظة التي يمكن لهذا الدور إنشاؤها.",
1046+
"@savedHeadlinesFilterLimitsDescription": {
1047+
"description": "وصف لحد المرشحات المحفوظة"
1048+
},
10411049
"adPlacementIntervalLabel": "فترة وضع الإعلان",
10421050
"@adPlacementIntervalLabel": {
10431051
"description": "تسمية فترة وضع الإعلان"

‎lib/l10n/arb/app_en.arb‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,14 @@
10381038
"@adFrequencyDescription": {
10391039
"description": "Description for Ad Frequency"
10401040
},
1041+
"savedHeadlinesFilterLimitsDescription": "Set limits on the number of saved headlines filters for each user tier.",
1042+
"@savedHeadlinesFilterLimitsDescription": {
1043+
"description": "Description for the Saved Headlines Filter Limits section"
1044+
},
1045+
"savedHeadlinesFilterLimitsTitle": "Saved Headlines Filter Limits",
1046+
"@savedHeadlinesFilterLimitsTitle": {
1047+
"description": "Title for the Saved Headlines Filter Limits section"
1048+
},
10411049
"adPlacementIntervalLabel": "Ad Placement Interval",
10421050
"@adPlacementIntervalLabel": {
10431051
"description": "Label for Ad Placement Interval"

‎lib/local_ads_management/view/local_ads_management_page.dart‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_manage
88
import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/native_ads_page.dart';
99
import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/video_ads_page.dart';
1010
import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart';
11-
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/extensions.dart';
1211
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
1312
import 'package:go_router/go_router.dart';
1413
import 'package:ui_kit/ui_kit.dart';

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /