From 901627fcce3f5da710c7654386af53a1a588f264 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:29:53 +0100 Subject: [PATCH 01/40] chore(dependencies): update go_router to version 16.0.0 and device_frame to version 1.4.0 --- pubspec.lock | 20 ++++++++++---------- pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9d67c2f2..e166c2b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,18 +69,18 @@ packages: dependency: transitive description: name: device_frame - sha256: a58796a9a2efc0fd8a7903cee0eed2e2d111f4a7d81fa2319ab89430b020f624 + sha256: "7b2ebb2a09d6cc0f086b51bd1412d7be83e0170056a7290349169be41164c86a" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" device_preview: dependency: "direct main" description: name: device_preview - sha256: a694acdd3894b4c7d600f4ee413afc4ff917f76026b97ab06575fe886429ef19 + sha256: "88aa1cc73ee9a8ec771b309dcbc4000cc66b5d8456b825980997640ab1195bf5" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" dio: dependency: transitive description: @@ -180,18 +180,18 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.1.0" go_router: dependency: "direct main" description: name: go_router - sha256: ac294be30ba841830cfa146e5a3b22bb09f8dc5a0fdd9ca9332b04b0bde99ebf + sha256: c489908a54ce2131f1d1b7cc631af9c1a06fac5ca7c449e959192089f9489431 url: "https://pub.dev" source: hosted - version: "15.2.4" + version: "16.0.0" google_fonts: dependency: "direct main" description: @@ -304,7 +304,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "096ceed8957af0935e950818b657617510b9a9ba" + resolved-ref: "9e771623f5745113d346fd4bfdb1abccf7e75049" url: "https://github.com/headlines-toolkit/ht-shared.git" source: git version: "0.0.0" @@ -611,4 +611,4 @@ packages: version: "1.1.0" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index d37f220d..8534f3d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: flutter_bloc: ^9.1.1 flutter_localizations: sdk: flutter - go_router: ^15.2.4 + go_router: ^16.0.0 google_fonts: ^6.2.1 ht_auth_api: git: From b3f211884f1703f06ac6716888718ccf03a5c476 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:30:12 +0100 Subject: [PATCH 02/40] feat(bloc-observer): add print statements for onChange and onError logging --- lib/bloc_observer.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bloc_observer.dart b/lib/bloc_observer.dart index 97afcf43..0439dac9 100644 --- a/lib/bloc_observer.dart +++ b/lib/bloc_observer.dart @@ -9,11 +9,13 @@ class AppBlocObserver extends BlocObserver { void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); log('onChange(${bloc.runtimeType}, $change)'); + print('onChange(${bloc.runtimeType}, $change)'); } @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) { log('onError(${bloc.runtimeType}, $error, $stackTrace)'); + print('onError(${bloc.runtimeType}, $error, $stackTrace)'); super.onError(bloc, error, stackTrace); } } From 3fd6859910421bf84a343b8a1c436248c0d4e45a Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:31:19 +0100 Subject: [PATCH 03/40] refactor: This first step isolates the change to the CreateCategoryState to ensure it can correctly manage the new ContentStatus property. This resolves the compilation error you previously identified by ensuring the state object is aware of the new field before the BLoC tries to use it. --- .../bloc/create_category/create_category_state.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/content_management/bloc/create_category/create_category_state.dart b/lib/content_management/bloc/create_category/create_category_state.dart index 7c4fdeef..47884d57 100644 --- a/lib/content_management/bloc/create_category/create_category_state.dart +++ b/lib/content_management/bloc/create_category/create_category_state.dart @@ -23,6 +23,7 @@ final class CreateCategoryState extends Equatable { this.name = '', this.description = '', this.iconUrl = '', + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -30,6 +31,7 @@ final class CreateCategoryState extends Equatable { final String name; final String description; final String iconUrl; + final ContentStatus contentStatus; final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -41,6 +43,7 @@ final class CreateCategoryState extends Equatable { String? name, String? description, String? iconUrl, + ContentStatus? contentStatus, String? errorMessage, }) { return CreateCategoryState( @@ -48,10 +51,18 @@ final class CreateCategoryState extends Equatable { name: name ?? this.name, description: description ?? this.description, iconUrl: iconUrl ?? this.iconUrl, + contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage, ); } @override - List get props => [status, name, description, iconUrl, errorMessage]; + List get props => [ + status, + name, + description, + iconUrl, + contentStatus, + errorMessage, + ]; } From c0d4824538be05bfd0d1297c00cd3150b4e42448 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:31:52 +0100 Subject: [PATCH 04/40] refactor: This step introduces the CreateCategoryStatusChanged event, which is necessary for the UI to communicate status changes to the CreateCategoryBloc. --- .../create_category/create_category_event.dart | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/content_management/bloc/create_category/create_category_event.dart b/lib/content_management/bloc/create_category/create_category_event.dart index e159d37c..591e5706 100644 --- a/lib/content_management/bloc/create_category/create_category_event.dart +++ b/lib/content_management/bloc/create_category/create_category_event.dart @@ -5,7 +5,7 @@ sealed class CreateCategoryEvent extends Equatable { const CreateCategoryEvent(); @override - List get props => []; + List get props => []; } /// Event for when the category's name is changed. @@ -13,7 +13,7 @@ final class CreateCategoryNameChanged extends CreateCategoryEvent { const CreateCategoryNameChanged(this.name); final String name; @override - List get props => [name]; + List get props => [name]; } /// Event for when the category's description is changed. @@ -21,7 +21,7 @@ final class CreateCategoryDescriptionChanged extends CreateCategoryEvent { const CreateCategoryDescriptionChanged(this.description); final String description; @override - List get props => [description]; + List get props => [description]; } /// Event for when the category's icon URL is changed. @@ -29,7 +29,16 @@ final class CreateCategoryIconUrlChanged extends CreateCategoryEvent { const CreateCategoryIconUrlChanged(this.iconUrl); final String iconUrl; @override - List get props => [iconUrl]; + List get props => [iconUrl]; +} + +/// Event for when the category's status is changed. +final class CreateCategoryStatusChanged extends CreateCategoryEvent { + const CreateCategoryStatusChanged(this.status); + + final ContentStatus status; + @override + List get props => [status]; } /// Event to signal that the form should be submitted. From be16c56212976ad1381ef13b8a5cd829be0af970 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:32:20 +0100 Subject: [PATCH 05/40] refactor: updates the CreateCategoryBloc to handle the new CreateCategoryStatusChanged event and to include the status field when submitting the new category to the repository. --- .../bloc/create_category/create_category_bloc.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/content_management/bloc/create_category/create_category_bloc.dart b/lib/content_management/bloc/create_category/create_category_bloc.dart index 164e459e..845c2580 100644 --- a/lib/content_management/bloc/create_category/create_category_bloc.dart +++ b/lib/content_management/bloc/create_category/create_category_bloc.dart @@ -17,6 +17,7 @@ class CreateCategoryBloc on(_onNameChanged); on(_onDescriptionChanged); on(_onIconUrlChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -58,6 +59,18 @@ class CreateCategoryBloc ); } + void _onStatusChanged( + CreateCategoryStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: CreateCategoryStatus.initial, + ), + ); + } + Future _onSubmitted( CreateCategorySubmitted event, Emitter emit, @@ -70,6 +83,7 @@ class CreateCategoryBloc name: state.name, description: state.description.isNotEmpty ? state.description : null, iconUrl: state.iconUrl.isNotEmpty ? state.iconUrl : null, + status: state.contentStatus, ); await _categoriesRepository.create(item: newCategory); From 1ea810d63544a88a306d19039e75ed839c79b54b Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:33:04 +0100 Subject: [PATCH 06/40] feat: adds the DropdownButtonFormField to the CreateCategoryPage UI. This allows administrators to select a ContentStatus when creating a new category. The dropdown is populated with all possible values from the ContentStatus enum. --- .../view/create_category_page.dart | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/create_category_page.dart b/lib/content_management/view/create_category_page.dart index f2e2e77b..363ce76b 100644 --- a/lib/content_management/view/create_category_page.dart +++ b/lib/content_management/view/create_category_page.dart @@ -83,9 +83,9 @@ class _CreateCategoryViewState extends State<_CreateCategoryView> { ), ); context.read().add( - const LoadCategoriesRequested( - limit: kDefaultRowsPerPage, - ), + const LoadCategoriesRequested( + limit: kDefaultRowsPerPage, + ), ); context.pop(); } @@ -142,6 +142,53 @@ class _CreateCategoryViewState extends State<_CreateCategoryView> { .read() .add(CreateCategoryIconUrlChanged(value)), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) => context + .read() + .add(CreateCategoryStatusChanged(value!)), + ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + CreateCategoryStatusChanged(value), + ); + }, + ), ], ), ), From 173670fc4c6c9d4449ebfc06f3b1eec99a1ae0cb Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:33:47 +0100 Subject: [PATCH 07/40] refactor: updates the EditCategoryState to manage the ContentStatus of the category being edited. This involves adding the new field to the state class, its constructor, the copyWith method, and the props list for Equatable. --- .../bloc/edit_category/edit_category_state.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/content_management/bloc/edit_category/edit_category_state.dart b/lib/content_management/bloc/edit_category/edit_category_state.dart index 9f6537f3..c38f0e28 100644 --- a/lib/content_management/bloc/edit_category/edit_category_state.dart +++ b/lib/content_management/bloc/edit_category/edit_category_state.dart @@ -26,6 +26,7 @@ final class EditCategoryState extends Equatable { this.name = '', this.description = '', this.iconUrl = '', + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -34,6 +35,7 @@ final class EditCategoryState extends Equatable { final String name; final String description; final String iconUrl; + final ContentStatus contentStatus; final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -45,6 +47,7 @@ final class EditCategoryState extends Equatable { String? name, String? description, String? iconUrl, + ContentStatus? contentStatus, String? errorMessage, }) { return EditCategoryState( @@ -53,12 +56,19 @@ final class EditCategoryState extends Equatable { name: name ?? this.name, description: description ?? this.description, iconUrl: iconUrl ?? this.iconUrl, + contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage ?? this.errorMessage, ); } @override - List get props => - [status, initialCategory, name, description, iconUrl, errorMessage]; + List get props => [ + status, + initialCategory, + name, + description, + iconUrl, + contentStatus, + errorMessage, + ]; } - From 4d158ab826c98062a5e33dfdd6db80914734350c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:35:14 +0100 Subject: [PATCH 08/40] feat: introduces the EditCategoryStatusChanged event. This is necessary for the UI to inform the EditCategoryBloc when the administrator changes the category's status in the edit form. --- .../edit_category/edit_category_event.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/content_management/bloc/edit_category/edit_category_event.dart b/lib/content_management/bloc/edit_category/edit_category_event.dart index cea54d44..f8301950 100644 --- a/lib/content_management/bloc/edit_category/edit_category_event.dart +++ b/lib/content_management/bloc/edit_category/edit_category_event.dart @@ -5,7 +5,7 @@ sealed class EditCategoryEvent extends Equatable { const EditCategoryEvent(); @override - List get props => []; + List get props => []; } /// Event to load the initial category data for editing. @@ -20,7 +20,7 @@ final class EditCategoryNameChanged extends EditCategoryEvent { final String name; @override - List get props => [name]; + List get props => [name]; } /// Event triggered when the category description input changes. @@ -30,7 +30,7 @@ final class EditCategoryDescriptionChanged extends EditCategoryEvent { final String description; @override - List get props => [description]; + List get props => [description]; } /// Event triggered when the category icon URL input changes. @@ -40,7 +40,17 @@ final class EditCategoryIconUrlChanged extends EditCategoryEvent { final String iconUrl; @override - List get props => [iconUrl]; + List get props => [iconUrl]; +} + +/// Event for when the category's status is changed. +final class EditCategoryStatusChanged extends EditCategoryEvent { + const EditCategoryStatusChanged(this.status); + + final ContentStatus status; + + @override + List get props => [status]; } /// Event to submit the edited category data. From f8b07a65437b4b8642e3f93464cb9bb764215db5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:36:22 +0100 Subject: [PATCH 09/40] feat: updates the EditCategoryBloc to fully manage the ContentStatus. This includes handling the new EditCategoryStatusChanged event, loading the status when the category data is fetched, and including the status in the payload when the form is submitted. --- .../bloc/edit_category/edit_category_bloc.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/content_management/bloc/edit_category/edit_category_bloc.dart b/lib/content_management/bloc/edit_category/edit_category_bloc.dart index 6266e16b..3f67aa0b 100644 --- a/lib/content_management/bloc/edit_category/edit_category_bloc.dart +++ b/lib/content_management/bloc/edit_category/edit_category_bloc.dart @@ -19,6 +19,7 @@ class EditCategoryBloc extends Bloc { on(_onNameChanged); on(_onDescriptionChanged); on(_onIconUrlChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -39,6 +40,7 @@ class EditCategoryBloc extends Bloc { name: category.name, description: category.description ?? '', iconUrl: category.iconUrl ?? '', + contentStatus: category.status, ), ); } on HtHttpException catch (e) { @@ -95,6 +97,18 @@ class EditCategoryBloc extends Bloc { ); } + void _onStatusChanged( + EditCategoryStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: EditCategoryStatus.initial, + ), + ); + } + Future _onSubmitted( EditCategorySubmitted event, Emitter emit, @@ -120,6 +134,7 @@ class EditCategoryBloc extends Bloc { name: state.name, description: state.description.isNotEmpty ? state.description : null, iconUrl: state.iconUrl.isNotEmpty ? state.iconUrl : null, + status: state.contentStatus, ); await _categoriesRepository.update( From 8404bb8c2267fb4f59127025dd86643b2484a3ec Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:38:06 +0100 Subject: [PATCH 10/40] feat: adds the DropdownButtonFormField to the EditCategoryPage UI. This allows administrators to see and change the ContentStatus of an existing category. --- .../view/edit_category_page.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/edit_category_page.dart b/lib/content_management/view/edit_category_page.dart index 902b4e02..8201447d 100644 --- a/lib/content_management/view/edit_category_page.dart +++ b/lib/content_management/view/edit_category_page.dart @@ -109,9 +109,9 @@ class _EditCategoryViewState extends State<_EditCategoryView> { const SnackBar(content: Text('Category updated successfully.')), ); context.read().add( - const LoadCategoriesRequested( - limit: kDefaultRowsPerPage, - ), + const LoadCategoriesRequested( + limit: kDefaultRowsPerPage, + ), ); context.pop(); } @@ -192,6 +192,31 @@ class _EditCategoryViewState extends State<_EditCategoryView> { .read() .add(EditCategoryIconUrlChanged(value)), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + EditCategoryStatusChanged(value), + ); + }, + ), ], ), ), From 0f0a79c99d29ea32749bbb39bdbe4ab1a504af3c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:42:21 +0100 Subject: [PATCH 11/40] refactor: updates the CategoriesPage data table to display the most relevant columns for content management: Name, Status, and Last Updated. This provides administrators with a clear, at-a-glance overview of each category's state. --- .../view/categories_page.dart | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/categories_page.dart b/lib/content_management/view/categories_page.dart index 4cfc6ccd..1dcc1f67 100644 --- a/lib/content_management/view/categories_page.dart +++ b/lib/content_management/view/categories_page.dart @@ -70,12 +70,17 @@ class _CategoriesPageState extends State { size: ColumnSize.L, ), DataColumn2( - label: Text(l10n.description), + label: Text(l10n.status), + size: ColumnSize.S, + ), + DataColumn2( + label: Text(l10n.lastUpdated), size: ColumnSize.M, ), DataColumn2( label: Text(l10n.actions), size: ColumnSize.S, + fixedWidth: 120, ), ], source: _CategoriesDataSource( @@ -137,16 +142,25 @@ class _CategoriesDataSource extends DataTableSource { // If we are loading, show a spinner. Otherwise, we've reached the end. if (isLoading) { return DataRow2( - cells: List.generate(3, (_) => const DataCell(Center(child: CircularProgressIndicator()))), + cells: List.generate( + 4, + (_) => const DataCell(Center(child: CircularProgressIndicator())), + ), ); } return null; } final category = categories[index]; return DataRow2( + onSelectChanged: (selected) { + if (selected ?? false) { + context.goNamed(Routes.editCategoryName, pathParameters: {'id': category.id}); + } + }, cells: [ DataCell(Text(category.name)), - DataCell(Text(category.description ?? l10n.notAvailable)), + DataCell(Text(category.status.l10n(context))), + DataCell(Text(category.updatedAt?.toLocal().toString() ?? l10n.notAvailable)), DataCell( Row( children: [ From e451e73adeb897927b6ddd61c5d83300183e5cb6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:51:01 +0100 Subject: [PATCH 12/40] feat: modifies the existing CreateSourceState to include the contentStatus field, its default value, and updates the copyWith method and props list accordingly. --- .../bloc/create_source/create_source_state.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/content_management/bloc/create_source/create_source_state.dart b/lib/content_management/bloc/create_source/create_source_state.dart index 469d3ea3..8a6f8374 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -30,6 +30,7 @@ final class CreateSourceState extends Equatable { this.language = '', this.headquarters, this.countries = const [], + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -41,6 +42,8 @@ final class CreateSourceState extends Equatable { final String language; final Country? headquarters; final List countries; + final ContentStatus contentStatus; + final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -55,6 +58,8 @@ final class CreateSourceState extends Equatable { String? language, ValueGetter? headquarters, List? countries, + ContentStatus? contentStatus, + String? errorMessage, }) { return CreateSourceState( @@ -66,6 +71,8 @@ final class CreateSourceState extends Equatable { language: language ?? this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, + contentStatus: contentStatus ?? this.contentStatus, + errorMessage: errorMessage, ); } @@ -80,6 +87,7 @@ final class CreateSourceState extends Equatable { language, headquarters, countries, + contentStatus, errorMessage, ]; } From b6d327a88b043d76b7118ab7972f8b641c60a426 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:52:35 +0100 Subject: [PATCH 13/40] feature: adds the CreateSourceStatusChanged event to the create_source_event.dart file. This event will be dispatched from the UI to the BLoC whenever the user changes the value in the new status dropdown. --- .../create_source/create_source_event.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/content_management/bloc/create_source/create_source_event.dart b/lib/content_management/bloc/create_source/create_source_event.dart index 055ebf71..8838dd62 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -18,7 +18,7 @@ final class CreateSourceNameChanged extends CreateSourceEvent { const CreateSourceNameChanged(this.name); final String name; @override - List get props => [name]; + List get props => [name]; } /// Event for when the source's description is changed. @@ -26,7 +26,7 @@ final class CreateSourceDescriptionChanged extends CreateSourceEvent { const CreateSourceDescriptionChanged(this.description); final String description; @override - List get props => [description]; + List get props => [description]; } /// Event for when the source's URL is changed. @@ -34,7 +34,7 @@ final class CreateSourceUrlChanged extends CreateSourceEvent { const CreateSourceUrlChanged(this.url); final String url; @override - List get props => [url]; + List get props => [url]; } /// Event for when the source's type is changed. @@ -50,7 +50,7 @@ final class CreateSourceLanguageChanged extends CreateSourceEvent { const CreateSourceLanguageChanged(this.language); final String language; @override - List get props => [language]; + List get props => [language]; } /// Event for when the source's headquarters is changed. @@ -61,6 +61,16 @@ final class CreateSourceHeadquartersChanged extends CreateSourceEvent { List get props => [headquarters]; } +/// Event for when the source's status is changed. +final class CreateSourceStatusChanged extends CreateSourceEvent { + const CreateSourceStatusChanged(this.status); + + final ContentStatus status; + + @override + List get props => [status]; +} + /// Event to signal that the form should be submitted. final class CreateSourceSubmitted extends CreateSourceEvent { const CreateSourceSubmitted(); From a080a68351968f7362f0b77744128cc373830779 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:53:53 +0100 Subject: [PATCH 14/40] feat: updates the CreateSourceBloc to handle the new CreateSourceStatusChanged event and to include the status field when creating the Source object for submission. --- .../bloc/create_source/create_source_bloc.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/content_management/bloc/create_source/create_source_bloc.dart b/lib/content_management/bloc/create_source/create_source_bloc.dart index 4448de94..533dff1d 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -23,6 +23,7 @@ class CreateSourceBloc extends Bloc { on(_onSourceTypeChanged); on(_onLanguageChanged); on(_onHeadquartersChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -103,6 +104,18 @@ class CreateSourceBloc extends Bloc { emit(state.copyWith(headquarters: () => event.headquarters)); } + void _onStatusChanged( + CreateSourceStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: CreateSourceStatus.initial, + ), + ); + } + Future _onSubmitted( CreateSourceSubmitted event, Emitter emit, @@ -118,6 +131,7 @@ class CreateSourceBloc extends Bloc { sourceType: state.sourceType, language: state.language.isNotEmpty ? state.language : null, headquarters: state.headquarters, + status: state.contentStatus, ); await _sourcesRepository.create(item: newSource); From 6ef5246e0fe9bd089e18818225ad5d53d7c43d56 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:55:56 +0100 Subject: [PATCH 15/40] feat: adds the DropdownButtonFormField to the CreateSourcePage UI, allowing administrators to set the ContentStatus when creating a new source. --- .../view/create_source_page.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 1239b33b..c9f42947 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -211,6 +211,31 @@ class _CreateSourceViewState extends State<_CreateSourceView> { .read() .add(CreateSourceHeadquartersChanged(value)), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + CreateSourceStatusChanged(value), + ); + }, + ), ], ), ), From 6816216c80526cf1dab2766d4174a0a36fab0660 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:57:43 +0100 Subject: [PATCH 16/40] feat: modifies the EditSourceState to include the contentStatus field. This is a crucial step to allow the EditSourceBloc to manage the lifecycle status of a source being edited. --- .../bloc/edit_source/edit_source_state.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/content_management/bloc/edit_source/edit_source_state.dart b/lib/content_management/bloc/edit_source/edit_source_state.dart index 950cb297..85908857 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -30,6 +30,7 @@ final class EditSourceState extends Equatable { this.language = '', this.headquarters, this.countries = const [], + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -42,6 +43,7 @@ final class EditSourceState extends Equatable { final String language; final Country? headquarters; final List countries; + final ContentStatus contentStatus; final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -57,6 +59,7 @@ final class EditSourceState extends Equatable { String? language, ValueGetter? headquarters, List? countries, + ContentStatus? contentStatus, String? errorMessage, }) { return EditSourceState( @@ -69,6 +72,7 @@ final class EditSourceState extends Equatable { language: language ?? this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, + contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage ?? this.errorMessage, ); } @@ -84,6 +88,7 @@ final class EditSourceState extends Equatable { language, headquarters, countries, + contentStatus, errorMessage, ]; } From 325b5192c84eccc21b9c75c34c119b45aa2f961a Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 08:59:29 +0100 Subject: [PATCH 17/40] feat: adds the EditSourceStatusChanged event to the edit_source_event.dart file. This event is crucial for allowing the UI to communicate status changes from the form to the EditSourceBloc. --- .../bloc/edit_source/edit_source_event.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/content_management/bloc/edit_source/edit_source_event.dart b/lib/content_management/bloc/edit_source/edit_source_event.dart index 678e4ef8..6a7246a1 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -20,7 +20,7 @@ final class EditSourceNameChanged extends EditSourceEvent { final String name; @override - List get props => [name]; + List get props => [name]; } /// Event triggered when the source description input changes. @@ -30,7 +30,7 @@ final class EditSourceDescriptionChanged extends EditSourceEvent { final String description; @override - List get props => [description]; + List get props => [description]; } /// Event triggered when the source URL input changes. @@ -40,7 +40,7 @@ final class EditSourceUrlChanged extends EditSourceEvent { final String url; @override - List get props => [url]; + List get props => [url]; } /// Event triggered when the source type input changes. @@ -60,7 +60,7 @@ final class EditSourceLanguageChanged extends EditSourceEvent { final String language; @override - List get props => [language]; + List get props => [language]; } /// Event triggered when the source headquarters input changes. @@ -73,6 +73,16 @@ final class EditSourceHeadquartersChanged extends EditSourceEvent { List get props => [headquarters]; } +/// Event for when the source's status is changed. +final class EditSourceStatusChanged extends EditSourceEvent { + const EditSourceStatusChanged(this.status); + + final ContentStatus status; + + @override + List get props => [status]; +} + /// Event to submit the edited source data. final class EditSourceSubmitted extends EditSourceEvent { const EditSourceSubmitted(); From 2da7da728c055b93069d1985889faa2e737db7bb Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:01:11 +0100 Subject: [PATCH 18/40] feat: updates the EditSourceBloc to fully manage the ContentStatus. This involves loading the initial status when the source is fetched, adding an event handler to react to changes from the UI, and including the status in the payload when the form is submitted. --- .../bloc/edit_source/edit_source_bloc.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/content_management/bloc/edit_source/edit_source_bloc.dart b/lib/content_management/bloc/edit_source/edit_source_bloc.dart index f00cf3ce..73e7a3de 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -28,6 +28,7 @@ class EditSourceBloc extends Bloc { on(_onSourceTypeChanged); on(_onLanguageChanged); on(_onHeadquartersChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -57,6 +58,7 @@ class EditSourceBloc extends Bloc { sourceType: () => source.sourceType, language: source.language ?? '', headquarters: () => source.headquarters, + contentStatus: source.status, countries: countries, ), ); @@ -139,6 +141,18 @@ class EditSourceBloc extends Bloc { ); } + void _onStatusChanged( + EditSourceStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: EditSourceStatus.initial, + ), + ); + } + Future _onSubmitted( EditSourceSubmitted event, Emitter emit, @@ -165,6 +179,7 @@ class EditSourceBloc extends Bloc { sourceType: state.sourceType, language: state.language.isNotEmpty ? state.language : null, headquarters: state.headquarters, + status: state.contentStatus, ); await _sourcesRepository.update(id: _sourceId, item: updatedSource); From 2384e50a8fded1dd5af3516e12aaf010d039b382 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:02:24 +0100 Subject: [PATCH 19/40] feat: updates the EditSourceBloc to fully manage the ContentStatus. This involves loading the initial status when the source is fetched, adding an event handler to react to changes from the UI, and including the status in the payload when the form is submitted. --- .../view/edit_source_page.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index db148b7d..a06a0b03 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -112,9 +112,9 @@ class _EditSourceViewState extends State<_EditSourceView> { SnackBar(content: Text(l10n.sourceUpdatedSuccessfully)), ); context.read().add( - const LoadSourcesRequested( - limit: kDefaultRowsPerPage, - ), + const LoadSourcesRequested( + limit: kDefaultRowsPerPage, + ), ); context.pop(); } @@ -258,6 +258,31 @@ class _EditSourceViewState extends State<_EditSourceView> { EditSourceHeadquartersChanged(value), ), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + EditSourceStatusChanged(value), + ); + }, + ), ], ), ), From 74afe79eca8fa6380d9c70d11b492e40f02aedff Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:05:02 +0100 Subject: [PATCH 20/40] feat: updates the SourcesPage data table to display the most relevant columns for an administrator: Name, Type, Status, and Last Updated. This provides a clear, at-a-glance overview of each source's state. --- lib/content_management/view/sources_page.dart | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/content_management/view/sources_page.dart b/lib/content_management/view/sources_page.dart index e3d42c10..9acc052b 100644 --- a/lib/content_management/view/sources_page.dart +++ b/lib/content_management/view/sources_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; +import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart'; import 'package:ht_dashboard/l10n/app_localizations.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/router/routes.dart'; @@ -74,12 +75,17 @@ class _SourcesPageState extends State { size: ColumnSize.M, ), DataColumn2( - label: Text(l10n.language), + label: Text(l10n.status), size: ColumnSize.S, ), + DataColumn2( + label: Text(l10n.lastUpdated), + size: ColumnSize.M, + ), DataColumn2( label: Text(l10n.actions), size: ColumnSize.S, + fixedWidth: 120, ), ], source: _SourcesDataSource( @@ -141,20 +147,37 @@ class _SourcesDataSource extends DataTableSource { // If we are loading, show a spinner. Otherwise, we've reached the end. if (isLoading) { return DataRow2( - cells: List.generate( - 4, - (_) => const DataCell(Center(child: CircularProgressIndicator())), - ), + cells: List.generate(5, (_) { + return const DataCell(Center(child: CircularProgressIndicator())); + }), ); } return null; } final source = sources[index]; return DataRow2( + onSelectChanged: (selected) { + if (selected ?? false) { + context.goNamed( + Routes.editSourceName, + pathParameters: {'id': source.id}, + ); + } + }, cells: [ DataCell(Text(source.name)), - DataCell(Text(source.sourceType?.name ?? l10n.unknown)), - DataCell(Text(source.language ?? l10n.unknown)), + DataCell(Text(source.sourceType?.localizedName(l10n) ?? l10n.unknown)), + DataCell( + Text( + source.status.name.replaceFirst( + source.status.name[0], + source.status.name[0].toUpperCase(), + ), + ), + ), + DataCell( + Text(source.updatedAt?.toLocal().toString() ?? l10n.notAvailable), + ), DataCell( Row( children: [ From 6d9740c591df1028e2735e0f518e85038c3972e0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:08:36 +0100 Subject: [PATCH 21/40] feat: modifies the CreateHeadlineState to include the contentStatus field. This will allow the BLoC to manage the status of a new headline before it's created. --- .../create_headline_state.dart | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/content_management/bloc/create_headline/create_headline_state.dart b/lib/content_management/bloc/create_headline/create_headline_state.dart index c0ed1aec..f809b6e3 100644 --- a/lib/content_management/bloc/create_headline/create_headline_state.dart +++ b/lib/content_management/bloc/create_headline/create_headline_state.dart @@ -30,6 +30,7 @@ final class CreateHeadlineState extends Equatable { this.category, this.sources = const [], this.categories = const [], + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -42,6 +43,7 @@ final class CreateHeadlineState extends Equatable { final Category? category; final List sources; final List categories; + final ContentStatus contentStatus; final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -57,6 +59,7 @@ final class CreateHeadlineState extends Equatable { ValueGetter? category, List? sources, List? categories, + ContentStatus? contentStatus, String? errorMessage, }) { return CreateHeadlineState( @@ -69,22 +72,23 @@ final class CreateHeadlineState extends Equatable { category: category != null ? category() : this.category, sources: sources ?? this.sources, categories: categories ?? this.categories, + contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage ?? this.errorMessage, ); } @override List get props => [ - status, - title, - description, - url, - imageUrl, - source, - category, - sources, - categories, - errorMessage, - ]; + status, + title, + description, + url, + imageUrl, + source, + category, + sources, + categories, + contentStatus, + errorMessage, + ]; } - From f78983e56d4b12b0b96b7e61365b984ca80755ab Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:10:45 +0100 Subject: [PATCH 22/40] feat: adds the CreateHeadlineStatusChanged event to the create_headline_event.dart file. This event will be used by the UI to notify the BLoC of changes to the headline's status. I've also updated the existing event classes to be final and standardized their props for consistency. --- .../create_headline_event.dart | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/content_management/bloc/create_headline/create_headline_event.dart b/lib/content_management/bloc/create_headline/create_headline_event.dart index de065648..63d04c19 100644 --- a/lib/content_management/bloc/create_headline/create_headline_event.dart +++ b/lib/content_management/bloc/create_headline/create_headline_event.dart @@ -1,7 +1,7 @@ part of 'create_headline_bloc.dart'; /// Base class for all events related to the [CreateHeadlineBloc]. -abstract class CreateHeadlineEvent extends Equatable { +sealed class CreateHeadlineEvent extends Equatable { const CreateHeadlineEvent(); @override @@ -9,44 +9,44 @@ abstract class CreateHeadlineEvent extends Equatable { } /// Event to signal that the data for dropdowns should be loaded. -class CreateHeadlineDataLoaded extends CreateHeadlineEvent { +final class CreateHeadlineDataLoaded extends CreateHeadlineEvent { const CreateHeadlineDataLoaded(); } /// Event for when the headline's title is changed. -class CreateHeadlineTitleChanged extends CreateHeadlineEvent { +final class CreateHeadlineTitleChanged extends CreateHeadlineEvent { const CreateHeadlineTitleChanged(this.title); final String title; @override - List get props => [title]; + List get props => [title]; } /// Event for when the headline's description is changed. -class CreateHeadlineDescriptionChanged extends CreateHeadlineEvent { +final class CreateHeadlineDescriptionChanged extends CreateHeadlineEvent { const CreateHeadlineDescriptionChanged(this.description); final String description; @override - List get props => [description]; + List get props => [description]; } /// Event for when the headline's URL is changed. -class CreateHeadlineUrlChanged extends CreateHeadlineEvent { +final class CreateHeadlineUrlChanged extends CreateHeadlineEvent { const CreateHeadlineUrlChanged(this.url); final String url; @override - List get props => [url]; + List get props => [url]; } /// Event for when the headline's image URL is changed. -class CreateHeadlineImageUrlChanged extends CreateHeadlineEvent { +final class CreateHeadlineImageUrlChanged extends CreateHeadlineEvent { const CreateHeadlineImageUrlChanged(this.imageUrl); final String imageUrl; @override - List get props => [imageUrl]; + List get props => [imageUrl]; } /// Event for when the headline's source is changed. -class CreateHeadlineSourceChanged extends CreateHeadlineEvent { +final class CreateHeadlineSourceChanged extends CreateHeadlineEvent { const CreateHeadlineSourceChanged(this.source); final Source? source; @override @@ -54,15 +54,24 @@ class CreateHeadlineSourceChanged extends CreateHeadlineEvent { } /// Event for when the headline's category is changed. -class CreateHeadlineCategoryChanged extends CreateHeadlineEvent { +final class CreateHeadlineCategoryChanged extends CreateHeadlineEvent { const CreateHeadlineCategoryChanged(this.category); final Category? category; @override List get props => [category]; } +/// Event for when the headline's status is changed. +final class CreateHeadlineStatusChanged extends CreateHeadlineEvent { + const CreateHeadlineStatusChanged(this.status); + + final ContentStatus status; + + @override + List get props => [status]; +} + /// Event to signal that the form should be submitted. -class CreateHeadlineSubmitted extends CreateHeadlineEvent { +final class CreateHeadlineSubmitted extends CreateHeadlineEvent { const CreateHeadlineSubmitted(); } - From 57ede7545ee0fbc4542ba2bddd900047c1275d50 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:36:10 +0100 Subject: [PATCH 23/40] feat: updates the CreateHeadlineBloc to handle the new CreateHeadlineStatusChanged event. It also modifies the submission logic to include the selected status when creating a new Headline instance. --- .../bloc/create_headline/create_headline_bloc.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/content_management/bloc/create_headline/create_headline_bloc.dart b/lib/content_management/bloc/create_headline/create_headline_bloc.dart index 8d03524c..412416bd 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -26,6 +26,7 @@ class CreateHeadlineBloc on(_onImageUrlChanged); on(_onSourceChanged); on(_onCategoryChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -114,6 +115,18 @@ class CreateHeadlineBloc emit(state.copyWith(category: () => event.category)); } + void _onStatusChanged( + CreateHeadlineStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: CreateHeadlineStatus.initial, + ), + ); + } + Future _onSubmitted( CreateHeadlineSubmitted event, Emitter emit, @@ -129,6 +142,7 @@ class CreateHeadlineBloc imageUrl: state.imageUrl.isNotEmpty ? state.imageUrl : null, source: state.source, category: state.category, + status: state.contentStatus, ); await _headlinesRepository.create(item: newHeadline); From 3d4016e2e6d6b524581b63dc53243426dbb2160c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:37:45 +0100 Subject: [PATCH 24/40] feat: adds the DropdownButtonFormField for ContentStatus to the CreateHeadlinePage UI, allowing administrators to set the status when creating a new headline. --- .../view/create_headline_page.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index cfbd1ae0..f2231537 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -85,9 +85,9 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { ), ); context.read().add( - const LoadHeadlinesRequested( - limit: kDefaultRowsPerPage, - ), + const LoadHeadlinesRequested( + limit: kDefaultRowsPerPage, + ), ); context.pop(); } @@ -214,6 +214,31 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { .read() .add(CreateHeadlineCategoryChanged(value)), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + CreateHeadlineStatusChanged(value), + ); + }, + ), ], ), ), From 75bb6de3dd2a6cf54dda5725cab57e0a9f89200c Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:39:13 +0100 Subject: [PATCH 25/40] feat: modifies the EditHeadlineState to include the contentStatus field. This is necessary for the EditHeadlineBloc to manage the lifecycle status of the headline being edited. --- .../edit_headline/edit_headline_state.dart | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/content_management/bloc/edit_headline/edit_headline_state.dart b/lib/content_management/bloc/edit_headline/edit_headline_state.dart index c2463c7b..5bbb617a 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_state.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_state.dart @@ -31,6 +31,7 @@ final class EditHeadlineState extends Equatable { this.category, this.sources = const [], this.categories = const [], + this.contentStatus = ContentStatus.active, this.errorMessage, }); @@ -44,6 +45,7 @@ final class EditHeadlineState extends Equatable { final Category? category; final List sources; final List categories; + final ContentStatus contentStatus; final String? errorMessage; /// Returns true if the form is valid and can be submitted. @@ -60,6 +62,7 @@ final class EditHeadlineState extends Equatable { ValueGetter? category, List? sources, List? categories, + ContentStatus? contentStatus, String? errorMessage, }) { return EditHeadlineState( @@ -73,22 +76,24 @@ final class EditHeadlineState extends Equatable { category: category != null ? category() : this.category, sources: sources ?? this.sources, categories: categories ?? this.categories, + contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage ?? this.errorMessage, ); } @override List get props => [ - status, - initialHeadline, - title, - description, - url, - imageUrl, - source, - category, - sources, - categories, - errorMessage, - ]; + status, + initialHeadline, + title, + description, + url, + imageUrl, + source, + category, + sources, + categories, + contentStatus, + errorMessage, + ]; } From de0bbba71fd5ca4937a633ab3d8c219ad09535de Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:41:03 +0100 Subject: [PATCH 26/40] feat: introduces the EditHeadlineStatusChanged event, which is necessary for the UI to communicate status changes to the EditHeadlineBloc. I am also taking this opportunity to modernize the event file by converting the base class to sealed and making the event classes final for better type safety and consistency. --- .../edit_headline/edit_headline_event.dart | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/content_management/bloc/edit_headline/edit_headline_event.dart b/lib/content_management/bloc/edit_headline/edit_headline_event.dart index 8b719446..44e4e81d 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_event.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_event.dart @@ -1,7 +1,7 @@ part of 'edit_headline_bloc.dart'; /// Base class for all events related to the [EditHeadlineBloc]. -abstract class EditHeadlineEvent extends Equatable { +sealed class EditHeadlineEvent extends Equatable { const EditHeadlineEvent(); @override @@ -9,44 +9,44 @@ abstract class EditHeadlineEvent extends Equatable { } /// Event to signal that the headline data should be loaded. -class EditHeadlineLoaded extends EditHeadlineEvent { +final class EditHeadlineLoaded extends EditHeadlineEvent { const EditHeadlineLoaded(); } /// Event for when the headline's title is changed. -class EditHeadlineTitleChanged extends EditHeadlineEvent { +final class EditHeadlineTitleChanged extends EditHeadlineEvent { const EditHeadlineTitleChanged(this.title); final String title; @override - List get props => [title]; + List get props => [title]; } /// Event for when the headline's description is changed. -class EditHeadlineDescriptionChanged extends EditHeadlineEvent { +final class EditHeadlineDescriptionChanged extends EditHeadlineEvent { const EditHeadlineDescriptionChanged(this.description); final String description; @override - List get props => [description]; + List get props => [description]; } /// Event for when the headline's URL is changed. -class EditHeadlineUrlChanged extends EditHeadlineEvent { +final class EditHeadlineUrlChanged extends EditHeadlineEvent { const EditHeadlineUrlChanged(this.url); final String url; @override - List get props => [url]; + List get props => [url]; } /// Event for when the headline's image URL is changed. -class EditHeadlineImageUrlChanged extends EditHeadlineEvent { +final class EditHeadlineImageUrlChanged extends EditHeadlineEvent { const EditHeadlineImageUrlChanged(this.imageUrl); final String imageUrl; @override - List get props => [imageUrl]; + List get props => [imageUrl]; } /// Event for when the headline's source is changed. -class EditHeadlineSourceChanged extends EditHeadlineEvent { +final class EditHeadlineSourceChanged extends EditHeadlineEvent { const EditHeadlineSourceChanged(this.source); final Source? source; @override @@ -54,15 +54,24 @@ class EditHeadlineSourceChanged extends EditHeadlineEvent { } /// Event for when the headline's category is changed. -class EditHeadlineCategoryChanged extends EditHeadlineEvent { +final class EditHeadlineCategoryChanged extends EditHeadlineEvent { const EditHeadlineCategoryChanged(this.category); final Category? category; @override List get props => [category]; } +/// Event for when the headline's status is changed. +final class EditHeadlineStatusChanged extends EditHeadlineEvent { + const EditHeadlineStatusChanged(this.status); + + final ContentStatus status; + + @override + List get props => [status]; +} + /// Event to signal that the form should be submitted. -class EditHeadlineSubmitted extends EditHeadlineEvent { +final class EditHeadlineSubmitted extends EditHeadlineEvent { const EditHeadlineSubmitted(); } - From 73e116f2ed40484bda79de7ab7f6625f705c2df8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:42:50 +0100 Subject: [PATCH 27/40] feat: updates the EditHeadlineBloc to fully manage the ContentStatus. This includes loading the initial status when the headline data is fetched, adding an event handler for the new EditHeadlineStatusChanged event, and including the updated status in the payload when the form is submitted. --- .../bloc/edit_headline/edit_headline_bloc.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart index da2fee1b..b8f12f5b 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -27,6 +27,7 @@ class EditHeadlineBloc extends Bloc { on(_onImageUrlChanged); on(_onSourceChanged); on(_onCategoryChanged); + on(_onStatusChanged); on(_onSubmitted); } @@ -68,6 +69,7 @@ class EditHeadlineBloc extends Bloc { category: () => headline.category, sources: sources, categories: categories, + contentStatus: headline.status, ), ); } on HtHttpException catch (e) { @@ -151,6 +153,18 @@ class EditHeadlineBloc extends Bloc { ); } + void _onStatusChanged( + EditHeadlineStatusChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + contentStatus: event.status, + status: EditHeadlineStatus.initial, + ), + ); + } + Future _onSubmitted( EditHeadlineSubmitted event, Emitter emit, @@ -177,6 +191,7 @@ class EditHeadlineBloc extends Bloc { imageUrl: state.imageUrl.isNotEmpty ? state.imageUrl : null, source: state.source, category: state.category, + status: state.contentStatus, ); await _headlinesRepository.update(id: _headlineId, item: updatedHeadline); From 52be023ff22fb07b6d9ea4016484190c41cd0ff0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:44:15 +0100 Subject: [PATCH 28/40] feat: adds the DropdownButtonFormField for ContentStatus to the EditHeadlinePage UI. This allows administrators to view and modify the status of an existing headline. --- .../view/edit_headline_page.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index 83922ce0..b6bec3f3 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -273,6 +273,31 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { .read() .add(EditHeadlineCategoryChanged(value)), ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text( + status.name.replaceFirst( + status.name[0], + status.name[0].toUpperCase(), + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context + .read() + .add(EditHeadlineStatusChanged(value)); + }, + ), ], ), ), From 834b2acf7a5a7df3bb93b8d1fa5f90f01742ca6a Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:48:22 +0100 Subject: [PATCH 29/40] feat: adds the DropdownButtonFormField for ContentStatus to the EditHeadlinePage UI. This allows administrators to view and modify the status of an existing headline. --- .../view/headlines_page.dart | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index e9d3cc75..91de728e 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -6,12 +6,12 @@ import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dar import 'package:ht_dashboard/l10n/app_localizations.dart'; // Corrected import import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/router/routes.dart'; -import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/constants/app_spacing.dart'; import 'package:ht_dashboard/shared/utils/date_formatter.dart'; import 'package:ht_dashboard/shared/widgets/failure_state_widget.dart'; import 'package:ht_dashboard/shared/widgets/loading_state_widget.dart'; import 'package:ht_shared/ht_shared.dart'; +import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; /// {@template headlines_page} /// A page for displaying and managing Headlines in a tabular format. @@ -75,18 +75,24 @@ class _HeadlinesPageState extends State { size: ColumnSize.M, ), DataColumn2( - label: Text(l10n.publishedAt), + label: Text(l10n.status), size: ColumnSize.S, ), + DataColumn2( + label: Text(l10n.lastUpdated), + size: ColumnSize.M, + ), DataColumn2( label: Text(l10n.actions), size: ColumnSize.S, + fixedWidth: 120, ), ], source: _HeadlinesDataSource( context: context, headlines: state.headlines, - isLoading: state.headlinesStatus == ContentManagementStatus.loading, + isLoading: + state.headlinesStatus == ContentManagementStatus.loading, hasMore: state.headlinesHasMore, l10n: l10n, ), @@ -142,23 +148,38 @@ class _HeadlinesDataSource extends DataTableSource { // If we are loading, show a spinner. Otherwise, we've reached the end. if (isLoading) { return DataRow2( - cells: List.generate(4, (_) => const DataCell(Center(child: CircularProgressIndicator()))), + cells: List.generate( + 5, + (_) => const DataCell(Center(child: CircularProgressIndicator())), + ), ); } return null; } final headline = headlines[index]; return DataRow2( + onSelectChanged: (selected) { + if (selected ?? false) { + context.goNamed( + Routes.editHeadlineName, + pathParameters: {'id': headline.id}, + ); + } + }, cells: [ DataCell(Text(headline.title)), DataCell(Text(headline.source?.name ?? l10n.unknown)), DataCell( Text( - headline.publishedAt != null - ? DateFormatter.formatDate(headline.publishedAt!) - : l10n.unknown, + headline.status.name.replaceFirst( + headline.status.name[0], + headline.status.name[0].toUpperCase(), + ), ), ), + DataCell( + Text(headline.updatedAt?.toLocal().toString() ?? l10n.notAvailable), + ), DataCell( Row( children: [ @@ -199,7 +220,9 @@ class _HeadlinesDataSource extends DataTableSource { if (hasMore) { // When loading, we show an extra row for the spinner. // Otherwise, we just indicate that there are more rows. - return isLoading ? headlines.length + 1 : headlines.length + kDefaultRowsPerPage; + return isLoading + ? headlines.length + 1 + : headlines.length + kDefaultRowsPerPage; } return headlines.length; } From ae5554af10d47c7a8ddfd3fc52084990b58df204 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:52:11 +0100 Subject: [PATCH 30/40] feat: creates a new extension on the ContentStatus enum. This extension provides a .l10n() method that will be used to display user-friendly, localized names for each status in the UI. This will resolve the compilation errors we encountered earlier in the data tables. --- .../extensions/content_status_l10n.dart | 22 +++++++++++++++++++ lib/shared/extensions/extensions.dart | 1 + lib/shared/shared.dart | 1 + 3 files changed, 24 insertions(+) create mode 100644 lib/shared/extensions/content_status_l10n.dart create mode 100644 lib/shared/extensions/extensions.dart diff --git a/lib/shared/extensions/content_status_l10n.dart b/lib/shared/extensions/content_status_l10n.dart new file mode 100644 index 00000000..89055147 --- /dev/null +++ b/lib/shared/extensions/content_status_l10n.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:ht_dashboard/l10n/l10n.dart'; +import 'package:ht_shared/ht_shared.dart'; + +/// Provides a localized string representation for [ContentStatus]. +extension ContentStatusL10n on ContentStatus { + /// Returns the localized string for the status. + String l10n(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case ContentStatus.active: + // TODO(l10n): Add translation for contentStatusActive + return 'Active'; + case ContentStatus.archived: + // TODO(l10n): Add translation for contentStatusArchived + return 'Archived'; + case ContentStatus.draft: + // TODO(l10n): Add translation for contentStatusDraft + return 'Draft'; + } + } +} diff --git a/lib/shared/extensions/extensions.dart b/lib/shared/extensions/extensions.dart new file mode 100644 index 00000000..ad4d418f --- /dev/null +++ b/lib/shared/extensions/extensions.dart @@ -0,0 +1 @@ +export 'content_status_l10n.dart'; diff --git a/lib/shared/shared.dart b/lib/shared/shared.dart index a0f4b74e..e7c04f2a 100644 --- a/lib/shared/shared.dart +++ b/lib/shared/shared.dart @@ -5,6 +5,7 @@ library; export 'constants/constants.dart'; +export 'extensions/extensions.dart'; export 'theme/theme.dart'; export 'utils/utils.dart'; export 'widgets/widgets.dart'; From 6edae505ebe7b5ef7abf636ef852d752f8d24cc7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 09:58:27 +0100 Subject: [PATCH 31/40] feat: adds the required keys and their translations to the English and Arabic .arb files. This will provide the necessary strings for the new "Status" and "Last Updated" columns in the data tables, as well as the localized names for the ContentStatus enum values. --- lib/l10n/app_localizations.dart | 30 ++++++++++++++++++++++++++++++ lib/l10n/app_localizations_ar.dart | 15 +++++++++++++++ lib/l10n/app_localizations_en.dart | 15 +++++++++++++++ lib/l10n/arb/app_ar.arb | 12 +++++++++++- lib/l10n/arb/app_en.arb | 12 +++++++++++- 5 files changed, 82 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ed2ae688..16076a49 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1285,6 +1285,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Source created successfully.'** String get sourceCreatedSuccessfully; + + /// No description provided for @status. + /// + /// In en, this message translates to: + /// **'Status'** + String get status; + + /// No description provided for @lastUpdated. + /// + /// In en, this message translates to: + /// **'Last Updated'** + String get lastUpdated; + + /// No description provided for @contentStatusActive. + /// + /// In en, this message translates to: + /// **'Active'** + String get contentStatusActive; + + /// No description provided for @contentStatusArchived. + /// + /// In en, this message translates to: + /// **'Archived'** + String get contentStatusArchived; + + /// No description provided for @contentStatusDraft. + /// + /// In en, this message translates to: + /// **'Draft'** + String get contentStatusDraft; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 6509b2e9..c98a7e1c 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -671,4 +671,19 @@ class AppLocalizationsAr extends AppLocalizations { @override String get sourceCreatedSuccessfully => 'تم إنشاء المصدر بنجاح.'; + + @override + String get status => 'الحالة'; + + @override + String get lastUpdated => 'آخر تحديث'; + + @override + String get contentStatusActive => 'نشط'; + + @override + String get contentStatusArchived => 'مؤرشف'; + + @override + String get contentStatusDraft => 'مسودة'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f4767b14..f34b3fbf 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -669,4 +669,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sourceCreatedSuccessfully => 'Source created successfully.'; + + @override + String get status => 'Status'; + + @override + String get lastUpdated => 'Last Updated'; + + @override + String get contentStatusActive => 'Active'; + + @override + String get contentStatusArchived => 'Archived'; + + @override + String get contentStatusDraft => 'Draft'; } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index d00ab8dd..889328fc 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -815,5 +815,15 @@ "sourceCreatedSuccessfully": "تم إنشاء المصدر بنجاح.", "@sourceCreatedSuccessfully": { "description": "رسالة تُعرض عند إنشاء المصدر بنجاح" - } + }, + "status": "الحالة", + "@status": {}, + "lastUpdated": "آخر تحديث", + "@lastUpdated": {}, + "contentStatusActive": "نشط", + "@contentStatusActive": {}, + "contentStatusArchived": "مؤرشف", + "@contentStatusArchived": {}, + "contentStatusDraft": "مسودة", + "@contentStatusDraft": {} } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 14b65011..a7b6143a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -815,5 +815,15 @@ "sourceCreatedSuccessfully": "Source created successfully.", "@sourceCreatedSuccessfully": { "description": "Message displayed when a source is created successfully" - } + }, + "status": "Status", + "@status": {}, + "lastUpdated": "Last Updated", + "@lastUpdated": {}, + "contentStatusActive": "Active", + "@contentStatusActive": {}, + "contentStatusArchived": "Archived", + "@contentStatusArchived": {}, + "contentStatusDraft": "Draft", + "@contentStatusDraft": {} } \ No newline at end of file From f29fae5d56ce32404cf911e15f156d8c38a7d41b Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:06:55 +0100 Subject: [PATCH 32/40] refactor: updates the ContentStatusL10n extension to use the actual localization keys that we added to the .arb files in the previous step. This replaces the placeholder text and connects the enum to the localization system. --- lib/content_management/view/categories_page.dart | 1 + lib/shared/extensions/content_status_l10n.dart | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/content_management/view/categories_page.dart b/lib/content_management/view/categories_page.dart index 1dcc1f67..b47ba3a0 100644 --- a/lib/content_management/view/categories_page.dart +++ b/lib/content_management/view/categories_page.dart @@ -8,6 +8,7 @@ import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/router/routes.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/constants/app_spacing.dart'; +import 'package:ht_dashboard/shared/shared.dart'; import 'package:ht_dashboard/shared/widgets/failure_state_widget.dart'; import 'package:ht_dashboard/shared/widgets/loading_state_widget.dart'; import 'package:ht_shared/ht_shared.dart'; diff --git a/lib/shared/extensions/content_status_l10n.dart b/lib/shared/extensions/content_status_l10n.dart index 89055147..c2f9b6cf 100644 --- a/lib/shared/extensions/content_status_l10n.dart +++ b/lib/shared/extensions/content_status_l10n.dart @@ -9,14 +9,11 @@ extension ContentStatusL10n on ContentStatus { final l10n = context.l10n; switch (this) { case ContentStatus.active: - // TODO(l10n): Add translation for contentStatusActive - return 'Active'; + return l10n.contentStatusActive; case ContentStatus.archived: - // TODO(l10n): Add translation for contentStatusArchived - return 'Archived'; + return l10n.contentStatusArchived; case ContentStatus.draft: - // TODO(l10n): Add translation for contentStatusDraft - return 'Draft'; + return l10n.contentStatusDraft; } } -} +} \ No newline at end of file From ce656a5518f4122cd0efb4e8aa66dae179bff562 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:10:26 +0100 Subject: [PATCH 33/40] feat: integrates the localization extension into the CreateCategoryPage. I will update the DropdownButtonFormField to display the localized status names instead of the temporary placeholder text. I also noticed and removed a duplicated dropdown widget from this file to clean up the code. --- .../view/create_category_page.dart | 36 +++---------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/lib/content_management/view/create_category_page.dart b/lib/content_management/view/create_category_page.dart index 363ce76b..470ca996 100644 --- a/lib/content_management/view/create_category_page.dart +++ b/lib/content_management/view/create_category_page.dart @@ -5,6 +5,7 @@ import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dar import 'package:ht_dashboard/content_management/bloc/create_category/create_category_bloc.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/shared/shared.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -152,41 +153,14 @@ class _CreateCategoryViewState extends State<_CreateCategoryView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), - ); - }).toList(), - onChanged: (value) => context - .read() - .add(CreateCategoryStatusChanged(value!)), - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.contentStatus, - decoration: InputDecoration( - labelText: l10n.status, - border: const OutlineInputBorder(), - ), - items: ContentStatus.values.map((status) { - return DropdownMenuItem( - value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { if (value == null) return; - context.read().add( - CreateCategoryStatusChanged(value), - ); + context + .read() + .add(CreateCategoryStatusChanged(value)); }, ), ], From ef53b5e4007ae08a35b3eabdba0ffb755a4d077e Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:12:59 +0100 Subject: [PATCH 34/40] refactor: integrates the localization extension into the EditCategoryPage. I will update the DropdownButtonFormField to display the proper localized status names, replacing the temporary placeholder text. --- lib/content_management/view/edit_category_page.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/content_management/view/edit_category_page.dart b/lib/content_management/view/edit_category_page.dart index 8201447d..42b7184c 100644 --- a/lib/content_management/view/edit_category_page.dart +++ b/lib/content_management/view/edit_category_page.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/edit_category/edit_category_bloc.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/shared.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; @@ -202,12 +203,7 @@ class _EditCategoryViewState extends State<_EditCategoryView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { From 7f7a37a6fbbedb501923165a0d5eaa619659e8c9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:28:06 +0100 Subject: [PATCH 35/40] feat: integrates the localization extension into the CreateSourcePage. I will update the DropdownButtonFormField for ContentStatus to display the localized status names, replacing the temporary placeholder text. --- lib/content_management/view/categories_page.dart | 1 + lib/content_management/view/create_source_page.dart | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/content_management/view/categories_page.dart b/lib/content_management/view/categories_page.dart index b47ba3a0..b0b9b693 100644 --- a/lib/content_management/view/categories_page.dart +++ b/lib/content_management/view/categories_page.dart @@ -12,6 +12,7 @@ import 'package:ht_dashboard/shared/shared.dart'; import 'package:ht_dashboard/shared/widgets/failure_state_widget.dart'; import 'package:ht_dashboard/shared/widgets/loading_state_widget.dart'; import 'package:ht_shared/ht_shared.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; /// {@template categories_page} /// A page for displaying and managing Categories in a tabular format. diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index c9f42947..b91c5227 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/create_source/create_source_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/shared.dart'; @@ -221,12 +222,7 @@ class _CreateSourceViewState extends State<_CreateSourceView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { From 50a0fe3060874325bcdafe579cf27cfa0da87c9e Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:29:28 +0100 Subject: [PATCH 36/40] refactor: integrates the localization extension into the EditSourcePage. I will update the DropdownButtonFormField to display the proper localized status names, replacing the temporary placeholder text. --- lib/content_management/view/edit_source_page.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index a06a0b03..738e9d44 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/shared.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; @@ -268,12 +269,7 @@ class _EditSourceViewState extends State<_EditSourceView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { From c84fb40292b727dd3556298c680842dbe7458688 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:30:44 +0100 Subject: [PATCH 37/40] refactor: integrates the localization extension into the SourcesPage data table. I will update the DataCell for the status column to use the .l10n(context) extension method, ensuring the status is displayed in the user's selected language. --- lib/content_management/view/sources_page.dart | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/content_management/view/sources_page.dart b/lib/content_management/view/sources_page.dart index 9acc052b..ace07dfc 100644 --- a/lib/content_management/view/sources_page.dart +++ b/lib/content_management/view/sources_page.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart'; import 'package:ht_dashboard/l10n/app_localizations.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/router/routes.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; @@ -167,14 +168,7 @@ class _SourcesDataSource extends DataTableSource { cells: [ DataCell(Text(source.name)), DataCell(Text(source.sourceType?.localizedName(l10n) ?? l10n.unknown)), - DataCell( - Text( - source.status.name.replaceFirst( - source.status.name[0], - source.status.name[0].toUpperCase(), - ), - ), - ), + DataCell(Text(source.status.l10n(context))), DataCell( Text(source.updatedAt?.toLocal().toString() ?? l10n.notAvailable), ), From b8b00e5947e14cacd7a24838da3c63a3be076fb2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:32:29 +0100 Subject: [PATCH 38/40] refactor: integrates the localization extension into the CreateHeadlinePage. I will update the DropdownButtonFormField for ContentStatus to display the localized status names, replacing the temporary placeholder text. --- lib/content_management/view/create_headline_page.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index f2231537..06a79b71 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/create_headline/create_headline_bloc.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/shared.dart'; @@ -224,12 +225,7 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { From 75322074a18fa159f0dba9129675b4d53f6b2861 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:34:29 +0100 Subject: [PATCH 39/40] refactor: integrates the localization extension into the EditHeadlinePage. I will update the DropdownButtonFormField to display the proper localized status names, replacing the temporary placeholder text --- .../view/edit_headline_page.dart | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index b6bec3f3..7f1215bd 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/edit_headline/edit_headline_bloc.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_dashboard/shared/shared.dart'; @@ -283,19 +284,14 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { items: ContentStatus.values.map((status) { return DropdownMenuItem( value: status, - child: Text( - status.name.replaceFirst( - status.name[0], - status.name[0].toUpperCase(), - ), - ), + child: Text(status.l10n(context)), ); }).toList(), onChanged: (value) { if (value == null) return; - context - .read() - .add(EditHeadlineStatusChanged(value)); + context.read().add( + EditHeadlineStatusChanged(value), + ); }, ), ], From ae7d7bab638b0dfbd9710ade2a40e5ab1974d4ac Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 3 Jul 2025 10:35:32 +0100 Subject: [PATCH 40/40] refactor: integrates the localization extension into the HeadlinesPage data table. I will update the DataCell for the status column to use the .l10n(context) extension method, ensuring the status is displayed in the user's selected language. I will also clean up unused imports. --- lib/content_management/view/headlines_page.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index 91de728e..5dcd47f5 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -7,7 +7,7 @@ import 'package:ht_dashboard/l10n/app_localizations.dart'; // Corrected import import 'package:ht_dashboard/l10n/l10n.dart'; import 'package:ht_dashboard/router/routes.dart'; import 'package:ht_dashboard/shared/constants/app_spacing.dart'; -import 'package:ht_dashboard/shared/utils/date_formatter.dart'; +import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; import 'package:ht_dashboard/shared/widgets/failure_state_widget.dart'; import 'package:ht_dashboard/shared/widgets/loading_state_widget.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -169,14 +169,7 @@ class _HeadlinesDataSource extends DataTableSource { cells: [ DataCell(Text(headline.title)), DataCell(Text(headline.source?.name ?? l10n.unknown)), - DataCell( - Text( - headline.status.name.replaceFirst( - headline.status.name[0], - headline.status.name[0].toUpperCase(), - ), - ), - ), + DataCell(Text(headline.status.l10n(context))), DataCell( Text(headline.updatedAt?.toLocal().toString() ?? l10n.notAvailable), ),