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

Feat custom filters useres limits configuration #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
fulleni merged 27 commits into main from feat-custom-filters-useres-limits-configuration
Oct 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
79e7ccf
build(deps): update git dependencies to use version tags
fulleni Oct 13, 2025
8477221
docs: add CHANGELOG.md file
fulleni Oct 13, 2025
b763f1b
chore(deps): remove empty line in pubspec.yaml
fulleni Oct 13, 2025
74b0aa7
feat(ui): add user preset limits form
fulleni Oct 13, 2025
6a67ad5
refactor(app-configuration): move user preset limits form to lib dire...
fulleni Oct 13, 2025
e753d38
feat(app_configuration): add user presets configuration tab
fulleni Oct 13, 2025
f5344d1
feat(app_configuration): add user presets configuration tab
fulleni Oct 13, 2025
5908712
chore: changelog
fulleni Oct 13, 2025
81f8091
feat(l10n): add user presets and filter limit translations
fulleni Oct 13, 2025
ff48e8d
fix(app_configuration): update preset tab label
fulleni Oct 13, 2025
2c90f0e
fix(l10n): correct arb key for saved headlines filters limit description
fulleni Oct 13, 2025
784b3c3
refactor(app_configuration): convert UserPresetLimitsForm to Stateful...
fulleni Oct 13, 2025
880737d
feat(app_configuration): add user preset limits form and update expan...
fulleni Oct 13, 2025
f6e1593
fix(l10n): remove duplicate strings and correct order
fulleni Oct 13, 2025
3b9c25b
chore: misc
fulleni Oct 13, 2025
e084fbd
refactor(app_configuration): remove user presets configuration tab
fulleni Oct 13, 2025
ad58d64
refactor(app_configuration): rename widget and update localization keys
fulleni Oct 13, 2025
a745f84
refactor(app_configuration): update saved headlines filter limits UI ...
fulleni Oct 13, 2025
60bb031
refactor(l10n): remove unused arb entries and add new ones
fulleni Oct 13, 2025
6a902e0
feat(app_configuration): add descriptions to limits sections
fulleni Oct 13, 2025
7ddf14c
refactor(l10n): remove unused localization strings and fix descriptions
fulleni Oct 13, 2025
1b94082
fix(localization): update saved headlines filter limit labels and des...
fulleni Oct 13, 2025
7db311a
style: format
fulleni Oct 13, 2025
2573bcb
refactor(app_configuration): remove user content limits description text
fulleni Oct 13, 2025
3272453
refactor(app_configuration): implement dynamic tabbed interface for s...
fulleni Oct 13, 2025
70965cb
fix(localization): correct Arabic translation for 'savedHeadlinesFilt...
fulleni Oct 13, 2025
18aa371
docs(changelog): remove duplicate 1.0.0 release notes
fulleni Oct 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
##1.0.0

- **BREAKING** feat!: migrated from date based versioning to semantic versioning.
49 changes: 48 additions & 1 deletion lib/app_configuration/view/tabs/feed_configuration_tab.dart
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/feed_decorator_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/saved_headlines_filters_limit_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/user_preference_limits_form.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/feed_decorator_type_l10n.dart';
Expand Down Expand Up @@ -67,6 +68,15 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
},
initiallyExpanded: expandedIndex == tileIndex,
children: [
Text(
l10n.userContentLimitsDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: AppSpacing.lg),
UserPreferenceLimitsForm(
remoteConfig: widget.remoteConfig,
onConfigChanged: widget.onConfigChanged,
Expand All @@ -76,11 +86,48 @@ class _FeedConfigurationTabState extends State<FeedConfigurationTab> {
},
),
const SizedBox(height: AppSpacing.lg),
// New Top-level ExpansionTile for Feed Decorators
// New Top-level ExpansionTile for User Preset Limits
ValueListenableBuilder<int?>(
valueListenable: _expandedTileIndex,
builder: (context, expandedIndex, child) {
const tileIndex = 1;
return ExpansionTile(
key: ValueKey('savedHeadlinesFilterLimitsTile_$expandedIndex'),
title: Text(l10n.savedHeadlinesFilterLimitsTitle),
childrenPadding: const EdgeInsetsDirectional.only(
start: AppSpacing.lg,
top: AppSpacing.md,
bottom: AppSpacing.md,
),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
onExpansionChanged: (isExpanded) {
_expandedTileIndex.value = isExpanded ? tileIndex : null;
},
initiallyExpanded: expandedIndex == tileIndex,
children: [
Text(
l10n.savedHeadlinesFilterLimitsDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: AppSpacing.lg),
SavedHeadlinesFiltersLimitForm(
remoteConfig: widget.remoteConfig,
onConfigChanged: widget.onConfigChanged,
),
],
);
},
),
const SizedBox(height: AppSpacing.lg),
// New Top-level ExpansionTile for Feed Decorators
ValueListenableBuilder<int?>(
valueListenable: _expandedTileIndex,
builder: (context, expandedIndex, child) {
const tileIndex = 2;
return ExpansionTile(
key: ValueKey('feedDecoratorsTile_$expandedIndex'),
title: Text(l10n.feedDecoratorsTitle),
Expand Down
182 changes: 182 additions & 0 deletions lib/app_configuration/widgets/saved_headlines_filters_limit_form.dart
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/widgets/app_config_form_fields.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_user_role_l10n.dart';
import 'package:ui_kit/ui_kit.dart';

/// {@template saved_headlines_filters_limit_form}
/// A form for configuring saved headlines filter limits within the
/// [RemoteConfig].
///
/// This form provides fields to set the maximum number of saved filters
/// for guest, authenticated, and premium users.
/// {@endtemplate}
class SavedHeadlinesFiltersLimitForm extends StatefulWidget {
/// {@macro saved_headlines_filters_limit_form}
const SavedHeadlinesFiltersLimitForm({
required this.remoteConfig,
required this.onConfigChanged,
super.key,
});

/// The current [RemoteConfig] object.
final RemoteConfig remoteConfig;

/// Callback to notify parent of changes to the [RemoteConfig].
final ValueChanged<RemoteConfig> onConfigChanged;

@override
State<SavedHeadlinesFiltersLimitForm> createState() =>
_SavedHeadlinesFiltersLimitFormState();
}

class _SavedHeadlinesFiltersLimitFormState
extends State<SavedHeadlinesFiltersLimitForm>
with SingleTickerProviderStateMixin {
late TabController _tabController;
late final Map<AppUserRole, TextEditingController> _controllers;

@override
void initState() {
super.initState();
_tabController = TabController(
length: AppUserRole.values.length,
vsync: this,
);
_initializeControllers();
}

@override
void didUpdateWidget(covariant SavedHeadlinesFiltersLimitForm oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.remoteConfig.userPreferenceConfig !=
oldWidget.remoteConfig.userPreferenceConfig) {
_updateControllerValues();
}
}

void _initializeControllers() {
_controllers = {
for (final role in AppUserRole.values)
role: TextEditingController(
text: _getSavedFiltersLimit(
widget.remoteConfig.userPreferenceConfig,
role,
).toString(),
)..selection = TextSelection.collapsed(
offset: _getSavedFiltersLimit(
widget.remoteConfig.userPreferenceConfig,
role,
).toString().length,
),
};
}

void _updateControllerValues() {
for (final role in AppUserRole.values) {
final newLimit = _getSavedFiltersLimit(
widget.remoteConfig.userPreferenceConfig,
role,
).toString();
if (_controllers[role]?.text != newLimit) {
_controllers[role]?.text = newLimit;
_controllers[role]?.selection = TextSelection.collapsed(
offset: newLimit.length,
);
}
}
}

@override
void dispose() {
_tabController.dispose();
for (final controller in _controllers.values) {
controller.dispose();
}
super.dispose();
}

@override
Widget build(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: SizedBox(
height: kTextTabBarHeight,
child: TabBar(
controller: _tabController,
tabAlignment: TabAlignment.start,
isScrollable: true,
tabs: AppUserRole.values
.map((role) => Tab(text: role.l10n(context)))
.toList(),
),
),
),
const SizedBox(height: AppSpacing.lg),
SizedBox(
height: 120,
child: TabBarView(
controller: _tabController,
children: AppUserRole.values.map((role) {
final config = widget.remoteConfig.userPreferenceConfig;
return AppConfigIntField(
label: l10n.savedHeadlinesLimitLabel,
description: l10n.savedHeadlinesLimitDescription,
value: _getSavedFiltersLimit(config, role),
onChanged: (value) {
widget.onConfigChanged(
widget.remoteConfig.copyWith(
userPreferenceConfig:
_updateSavedFiltersLimit(config, value, role),
),
);
},
controller: _controllers[role],
);
}).toList(),
),
),
],
);
}

/// Retrieves the saved filters limit for a given [AppUserRole].
///
/// This helper method abstracts the logic for accessing the correct limit
/// from the [UserPreferenceConfig] based on the provided [role].
int _getSavedFiltersLimit(UserPreferenceConfig config, AppUserRole role) {
switch (role) {
case AppUserRole.guestUser:
return config.guestSavedFiltersLimit;
case AppUserRole.standardUser:
return config.authenticatedSavedFiltersLimit;
case AppUserRole.premiumUser:
return config.premiumSavedFiltersLimit;
}
}

/// Updates the saved filters limit for a given [AppUserRole].
///
/// This helper method abstracts the logic for updating the correct limit
/// within the [UserPreferenceConfig] based on the provided [role] and [value].
UserPreferenceConfig _updateSavedFiltersLimit(
UserPreferenceConfig config,
int value,
AppUserRole role,
) {
switch (role) {
case AppUserRole.guestUser:
return config.copyWith(guestSavedFiltersLimit: value);
case AppUserRole.standardUser:
return config.copyWith(authenticatedSavedFiltersLimit: value);
case AppUserRole.premiumUser:
return config.copyWith(premiumSavedFiltersLimit: value);
}
}
}
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,6 @@ class _UserPreferenceLimitsFormState extends State<UserPreferenceLimitsForm>
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.userContentLimitsDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: AppSpacing.lg),
Align(
alignment: AlignmentDirectional.centerStart,
child: SizedBox(
Expand Down
12 changes: 12 additions & 0 deletions lib/l10n/app_localizations.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,18 @@ abstract class AppLocalizations {
/// **'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).'**
String get adFrequencyDescription;

/// Description for the Saved Headlines Filter Limits section
///
/// In en, this message translates to:
/// **'Set limits on the number of saved headlines filters for each user tier.'**
String get savedHeadlinesFilterLimitsDescription;

/// Title for the Saved Headlines Filter Limits section
///
/// In en, this message translates to:
/// **'Saved Headlines Filter Limits'**
String get savedHeadlinesFilterLimitsTitle;

/// Label for Ad Placement Interval
///
/// In en, this message translates to:
Expand Down
7 changes: 7 additions & 0 deletions lib/l10n/app_localizations_ar.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,13 @@ class AppLocalizationsAr extends AppLocalizations {
String get adFrequencyDescription =>
'عدد مرات ظهور الإعلان لهذا الدور المستخدم (على سبيل المثال، قيمة 5 تعني أنه يمكن وضع إعلان بعد كل 5 عناصر إخبارية).';

@override
String get savedHeadlinesFilterLimitsDescription =>
'الحد الأقصى لعدد مرشحات العناوين المحفوظة التي يمكن لهذا الدور إنشاؤها.';

@override
String get savedHeadlinesFilterLimitsTitle => 'حدود مرشحات العناوين المحفوظة';

@override
String get adPlacementIntervalLabel => 'فترة وضع الإعلان';

Expand Down
7 changes: 7 additions & 0 deletions lib/l10n/app_localizations_en.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get adFrequencyDescription =>
'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).';

@override
String get savedHeadlinesFilterLimitsDescription =>
'Set limits on the number of saved headlines filters for each user tier.';

@override
String get savedHeadlinesFilterLimitsTitle => 'Saved Headlines Filter Limits';

@override
String get adPlacementIntervalLabel => 'Ad Placement Interval';

Expand Down
8 changes: 8 additions & 0 deletions lib/l10n/arb/app_ar.arb
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,14 @@
"@adFrequencyDescription": {
"description": "وصف تكرار الإعلان"
},
"savedHeadlinesFilterLimitsTitle": "حدود مرشحات العناوين المحفوظة",
"@savedHeadlinesFilterLimitsTitle": {
"description": "وصف لحد المرشحات المحفوظة"
},
"savedHeadlinesFilterLimitsDescription": "الحد الأقصى لعدد مرشحات العناوين المحفوظة التي يمكن لهذا الدور إنشاؤها.",
"@savedHeadlinesFilterLimitsDescription": {
"description": "وصف لحد المرشحات المحفوظة"
},
"adPlacementIntervalLabel": "فترة وضع الإعلان",
"@adPlacementIntervalLabel": {
"description": "تسمية فترة وضع الإعلان"
Expand Down
8 changes: 8 additions & 0 deletions lib/l10n/arb/app_en.arb
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,14 @@
"@adFrequencyDescription": {
"description": "Description for Ad Frequency"
},
"savedHeadlinesFilterLimitsDescription": "Set limits on the number of saved headlines filters for each user tier.",
"@savedHeadlinesFilterLimitsDescription": {
"description": "Description for the Saved Headlines Filter Limits section"
},
"savedHeadlinesFilterLimitsTitle": "Saved Headlines Filter Limits",
"@savedHeadlinesFilterLimitsTitle": {
"description": "Title for the Saved Headlines Filter Limits section"
},
"adPlacementIntervalLabel": "Ad Placement Interval",
"@adPlacementIntervalLabel": {
"description": "Label for Ad Placement Interval"
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_manage
import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/native_ads_page.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/video_ads_page.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/extensions.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
import 'package:go_router/go_router.dart';
import 'package:ui_kit/ui_kit.dart';
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class LocalAdActionButtons extends StatelessWidget {
case 'copyId':
// Copy the ad ID to the clipboard
Clipboard.setData(ClipboardData(text: itemId)).then((_) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.idCopied),
Expand Down
Loading
Loading

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