diff --git a/lib/content_management/bloc/content_management_bloc.dart b/lib/content_management/bloc/content_management_bloc.dart index ff0bf34..41af4a1 100644 --- a/lib/content_management/bloc/content_management_bloc.dart +++ b/lib/content_management/bloc/content_management_bloc.dart @@ -1,7 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; -import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; import 'package:ht_shared/ht_shared.dart'; part 'content_management_event.dart'; @@ -31,16 +30,16 @@ class ContentManagementBloc super(const ContentManagementState()) { on(_onContentManagementTabChanged); on(_onLoadHeadlinesRequested); - on(_onCreateHeadlineRequested); - on(_onUpdateHeadlineRequested); + on(_onHeadlineAdded); + on(_onHeadlineUpdated); on(_onDeleteHeadlineRequested); on(_onLoadCategoriesRequested); - on(_onCreateCategoryRequested); - on(_onUpdateCategoryRequested); + on(_onCategoryAdded); + on(_onCategoryUpdated); on(_onDeleteCategoryRequested); on(_onLoadSourcesRequested); - on(_onCreateSourceRequested); - on(_onUpdateSourceRequested); + on(_onSourceAdded); + on(_onSourceUpdated); on(_onOnDeleteSourceRequested); } @@ -93,59 +92,28 @@ class ContentManagementBloc } } - Future _onCreateHeadlineRequested( - CreateHeadlineRequested event, + void _onHeadlineAdded( + HeadlineAdded event, Emitter emit, - ) async { - emit(state.copyWith(headlinesStatus: ContentManagementStatus.loading)); - try { - await _headlinesRepository.create(item: event.headline); - // Reload headlines after creation - add( - const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - headlinesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - headlinesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); - } + ) { + final updatedHeadlines = [event.headline, ...state.headlines]; + emit( + state.copyWith( + headlines: updatedHeadlines, + headlinesStatus: ContentManagementStatus.success, + ), + ); } - Future _onUpdateHeadlineRequested( - UpdateHeadlineRequested event, + void _onHeadlineUpdated( + HeadlineUpdated event, Emitter emit, - ) async { - emit(state.copyWith(headlinesStatus: ContentManagementStatus.loading)); - try { - await _headlinesRepository.update(id: event.id, item: event.headline); - // Reload headlines after update - add( - const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - headlinesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - headlinesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); + ) { + final updatedHeadlines = List.from(state.headlines); + final index = updatedHeadlines.indexWhere((h) => h.id == event.headline.id); + if (index != -1) { + updatedHeadlines[index] = event.headline; + emit(state.copyWith(headlines: updatedHeadlines)); } } @@ -153,13 +121,12 @@ class ContentManagementBloc DeleteHeadlineRequested event, Emitter emit, ) async { - emit(state.copyWith(headlinesStatus: ContentManagementStatus.loading)); try { await _headlinesRepository.delete(id: event.id); - // Reload headlines after deletion - add( - const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), - ); + final updatedHeadlines = state.headlines + .where((h) => h.id != event.id) + .toList(); + emit(state.copyWith(headlines: updatedHeadlines)); } on HtHttpException catch (e) { emit( state.copyWith( @@ -215,59 +182,30 @@ class ContentManagementBloc } } - Future _onCreateCategoryRequested( - CreateCategoryRequested event, + void _onCategoryAdded( + CategoryAdded event, Emitter emit, - ) async { - emit(state.copyWith(categoriesStatus: ContentManagementStatus.loading)); - try { - await _categoriesRepository.create(item: event.category); - // Reload categories after creation - add( - const LoadCategoriesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - categoriesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - categoriesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); - } + ) { + final updatedCategories = [event.category, ...state.categories]; + emit( + state.copyWith( + categories: updatedCategories, + categoriesStatus: ContentManagementStatus.success, + ), + ); } - Future _onUpdateCategoryRequested( - UpdateCategoryRequested event, + void _onCategoryUpdated( + CategoryUpdated event, Emitter emit, - ) async { - emit(state.copyWith(categoriesStatus: ContentManagementStatus.loading)); - try { - await _categoriesRepository.update(id: event.id, item: event.category); - // Reload categories after update - add( - const LoadCategoriesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - categoriesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - categoriesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); + ) { + final updatedCategories = List.from(state.categories); + final index = updatedCategories.indexWhere( + (c) => c.id == event.category.id, + ); + if (index != -1) { + updatedCategories[index] = event.category; + emit(state.copyWith(categories: updatedCategories)); } } @@ -275,13 +213,12 @@ class ContentManagementBloc DeleteCategoryRequested event, Emitter emit, ) async { - emit(state.copyWith(categoriesStatus: ContentManagementStatus.loading)); try { await _categoriesRepository.delete(id: event.id); - // Reload categories after deletion - add( - const LoadCategoriesRequested(limit: kDefaultRowsPerPage), - ); + final updatedCategories = state.categories + .where((c) => c.id != event.id) + .toList(); + emit(state.copyWith(categories: updatedCategories)); } on HtHttpException catch (e) { emit( state.copyWith( @@ -337,59 +274,25 @@ class ContentManagementBloc } } - Future _onCreateSourceRequested( - CreateSourceRequested event, - Emitter emit, - ) async { - emit(state.copyWith(sourcesStatus: ContentManagementStatus.loading)); - try { - await _sourcesRepository.create(item: event.source); - // Reload sources after creation - add( - const LoadSourcesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - sourcesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - sourcesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); - } + void _onSourceAdded(SourceAdded event, Emitter emit) { + final updatedSources = [event.source, ...state.sources]; + emit( + state.copyWith( + sources: updatedSources, + sourcesStatus: ContentManagementStatus.success, + ), + ); } - Future _onUpdateSourceRequested( - UpdateSourceRequested event, + void _onSourceUpdated( + SourceUpdated event, Emitter emit, - ) async { - emit(state.copyWith(sourcesStatus: ContentManagementStatus.loading)); - try { - await _sourcesRepository.update(id: event.id, item: event.source); - // Reload sources after update - add( - const LoadSourcesRequested(limit: kDefaultRowsPerPage), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - sourcesStatus: ContentManagementStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - sourcesStatus: ContentManagementStatus.failure, - errorMessage: e.toString(), - ), - ); + ) { + final updatedSources = List.from(state.sources); + final index = updatedSources.indexWhere((s) => s.id == event.source.id); + if (index != -1) { + updatedSources[index] = event.source; + emit(state.copyWith(sources: updatedSources)); } } @@ -397,13 +300,12 @@ class ContentManagementBloc DeleteSourceRequested event, Emitter emit, ) async { - emit(state.copyWith(sourcesStatus: ContentManagementStatus.loading)); try { await _sourcesRepository.delete(id: event.id); - // Reload sources after deletion - add( - const LoadSourcesRequested(limit: kDefaultRowsPerPage), - ); + final updatedSources = state.sources + .where((s) => s.id != event.id) + .toList(); + emit(state.copyWith(sources: updatedSources)); } on HtHttpException catch (e) { emit( state.copyWith( diff --git a/lib/content_management/bloc/content_management_event.dart b/lib/content_management/bloc/content_management_event.dart index 3debb97..0fff533 100644 --- a/lib/content_management/bloc/content_management_event.dart +++ b/lib/content_management/bloc/content_management_event.dart @@ -38,35 +38,32 @@ final class LoadHeadlinesRequested extends ContentManagementEvent { List get props => [startAfterId, limit]; } -/// {@template create_headline_requested} -/// Event to request creation of a new headline. +/// {@template headline_added} +/// Event to add a new headline to the local state. /// {@endtemplate} -final class CreateHeadlineRequested extends ContentManagementEvent { - /// {@macro create_headline_requested} - const CreateHeadlineRequested(this.headline); +final class HeadlineAdded extends ContentManagementEvent { + /// {@macro headline_added} + const HeadlineAdded(this.headline); - /// The headline to create. + /// The headline that was added. final Headline headline; @override List get props => [headline]; } -/// {@template update_headline_requested} -/// Event to request update of an existing headline. +/// {@template headline_updated} +/// Event to update an existing headline in the local state. /// {@endtemplate} -final class UpdateHeadlineRequested extends ContentManagementEvent { - /// {@macro update_headline_requested} - const UpdateHeadlineRequested({required this.id, required this.headline}); +final class HeadlineUpdated extends ContentManagementEvent { + /// {@macro headline_updated} + const HeadlineUpdated(this.headline); - /// The ID of the headline to update. - final String id; - - /// The updated headline data. + /// The headline that was updated. final Headline headline; @override - List get props => [id, headline]; + List get props => [headline]; } /// {@template delete_headline_requested} @@ -100,35 +97,32 @@ final class LoadCategoriesRequested extends ContentManagementEvent { List get props => [startAfterId, limit]; } -/// {@template create_category_requested} -/// Event to request creation of a new category. +/// {@template category_added} +/// Event to add a new category to the local state. /// {@endtemplate} -final class CreateCategoryRequested extends ContentManagementEvent { - /// {@macro create_category_requested} - const CreateCategoryRequested(this.category); +final class CategoryAdded extends ContentManagementEvent { + /// {@macro category_added} + const CategoryAdded(this.category); - /// The category to create. + /// The category that was added. final Category category; @override List get props => [category]; } -/// {@template update_category_requested} -/// Event to request update of an existing category. +/// {@template category_updated} +/// Event to update an existing category in the local state. /// {@endtemplate} -final class UpdateCategoryRequested extends ContentManagementEvent { - /// {@macro update_category_requested} - const UpdateCategoryRequested({required this.id, required this.category}); - - /// The ID of the category to update. - final String id; +final class CategoryUpdated extends ContentManagementEvent { + /// {@macro category_updated} + const CategoryUpdated(this.category); - /// The updated category data. + /// The category that was updated. final Category category; @override - List get props => [id, category]; + List get props => [category]; } /// {@template delete_category_requested} @@ -162,35 +156,32 @@ final class LoadSourcesRequested extends ContentManagementEvent { List get props => [startAfterId, limit]; } -/// {@template create_source_requested} -/// Event to request creation of a new source. +/// {@template source_added} +/// Event to add a new source to the local state. /// {@endtemplate} -final class CreateSourceRequested extends ContentManagementEvent { - /// {@macro create_source_requested} - const CreateSourceRequested(this.source); +final class SourceAdded extends ContentManagementEvent { + /// {@macro source_added} + const SourceAdded(this.source); - /// The source to create. + /// The source that was added. final Source source; @override List get props => [source]; } -/// {@template update_source_requested} -/// Event to request update of an existing source. +/// {@template source_updated} +/// Event to update an existing source in the local state. /// {@endtemplate} -final class UpdateSourceRequested extends ContentManagementEvent { - /// {@macro update_source_requested} - const UpdateSourceRequested({required this.id, required this.source}); - - /// The ID of the source to update. - final String id; +final class SourceUpdated extends ContentManagementEvent { + /// {@macro source_updated} + const SourceUpdated(this.source); - /// The updated source data. + /// The source that was updated. final Source source; @override - List get props => [id, source]; + List get props => [source]; } /// {@template delete_source_requested} diff --git a/lib/content_management/bloc/content_management_state.dart b/lib/content_management/bloc/content_management_state.dart index f603193..d566cc6 100644 --- a/lib/content_management/bloc/content_management_state.dart +++ b/lib/content_management/bloc/content_management_state.dart @@ -1,6 +1,5 @@ part of 'content_management_bloc.dart'; - /// Represents the status of content loading and operations. enum ContentManagementStatus { /// The operation is in its initial state. @@ -115,19 +114,19 @@ class ContentManagementState extends Equatable { @override List get props => [ - activeTab, - headlinesStatus, - headlines, - headlinesCursor, - headlinesHasMore, - categoriesStatus, - categories, - categoriesCursor, - categoriesHasMore, - sourcesStatus, - sources, - sourcesCursor, - sourcesHasMore, - errorMessage, - ]; + activeTab, + headlinesStatus, + headlines, + headlinesCursor, + headlinesHasMore, + categoriesStatus, + categories, + categoriesCursor, + categoriesHasMore, + sourcesStatus, + sources, + sourcesCursor, + sourcesHasMore, + errorMessage, + ]; } 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 845c258..773d6e3 100644 --- a/lib/content_management/bloc/create_category/create_category_bloc.dart +++ b/lib/content_management/bloc/create_category/create_category_bloc.dart @@ -79,15 +79,23 @@ class CreateCategoryBloc emit(state.copyWith(status: CreateCategoryStatus.submitting)); try { + final now = DateTime.now(); final newCategory = Category( name: state.name, description: state.description.isNotEmpty ? state.description : null, iconUrl: state.iconUrl.isNotEmpty ? state.iconUrl : null, status: state.contentStatus, + createdAt: now, + updatedAt: now, ); await _categoriesRepository.create(item: newCategory); - emit(state.copyWith(status: CreateCategoryStatus.success)); + emit( + state.copyWith( + status: CreateCategoryStatus.success, + createdCategory: newCategory, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 47884d5..ec6a272 100644 --- a/lib/content_management/bloc/create_category/create_category_state.dart +++ b/lib/content_management/bloc/create_category/create_category_state.dart @@ -25,6 +25,7 @@ final class CreateCategoryState extends Equatable { this.iconUrl = '', this.contentStatus = ContentStatus.active, this.errorMessage, + this.createdCategory, }); final CreateCategoryStatus status; @@ -33,6 +34,7 @@ final class CreateCategoryState extends Equatable { final String iconUrl; final ContentStatus contentStatus; final String? errorMessage; + final Category? createdCategory; /// Returns true if the form is valid and can be submitted. /// Based on the Category model, only the name is required. @@ -45,6 +47,7 @@ final class CreateCategoryState extends Equatable { String? iconUrl, ContentStatus? contentStatus, String? errorMessage, + Category? createdCategory, }) { return CreateCategoryState( status: status ?? this.status, @@ -53,6 +56,7 @@ final class CreateCategoryState extends Equatable { iconUrl: iconUrl ?? this.iconUrl, contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage, + createdCategory: createdCategory ?? this.createdCategory, ); } @@ -64,5 +68,6 @@ final class CreateCategoryState extends Equatable { iconUrl, contentStatus, errorMessage, + createdCategory, ]; } 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 412416b..45f029d 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -135,18 +135,27 @@ class CreateHeadlineBloc emit(state.copyWith(status: CreateHeadlineStatus.submitting)); try { + final now = DateTime.now(); final newHeadline = Headline( title: state.title, description: state.description.isNotEmpty ? state.description : null, url: state.url.isNotEmpty ? state.url : null, imageUrl: state.imageUrl.isNotEmpty ? state.imageUrl : null, source: state.source, + publishedAt: now, + createdAt: now, + updatedAt: now, category: state.category, status: state.contentStatus, ); await _headlinesRepository.create(item: newHeadline); - emit(state.copyWith(status: CreateHeadlineStatus.success)); + emit( + state.copyWith( + status: CreateHeadlineStatus.success, + createdHeadline: newHeadline, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 f809b6e..605b078 100644 --- a/lib/content_management/bloc/create_headline/create_headline_state.dart +++ b/lib/content_management/bloc/create_headline/create_headline_state.dart @@ -32,6 +32,7 @@ final class CreateHeadlineState extends Equatable { this.categories = const [], this.contentStatus = ContentStatus.active, this.errorMessage, + this.createdHeadline, }); final CreateHeadlineStatus status; @@ -45,6 +46,7 @@ final class CreateHeadlineState extends Equatable { final List categories; final ContentStatus contentStatus; final String? errorMessage; + final Headline? createdHeadline; /// Returns true if the form is valid and can be submitted. bool get isFormValid => title.isNotEmpty; @@ -61,6 +63,7 @@ final class CreateHeadlineState extends Equatable { List? categories, ContentStatus? contentStatus, String? errorMessage, + Headline? createdHeadline, }) { return CreateHeadlineState( status: status ?? this.status, @@ -73,7 +76,8 @@ final class CreateHeadlineState extends Equatable { sources: sources ?? this.sources, categories: categories ?? this.categories, contentStatus: contentStatus ?? this.contentStatus, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: errorMessage, + createdHeadline: createdHeadline ?? this.createdHeadline, ); } @@ -90,5 +94,6 @@ final class CreateHeadlineState extends Equatable { categories, contentStatus, errorMessage, + createdHeadline, ]; } 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 533dff1..18db57f 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -37,7 +37,7 @@ class CreateSourceBloc extends Bloc { emit(state.copyWith(status: CreateSourceStatus.loading)); try { final countriesResponse = await _countriesRepository.readAll(); - final countries = (countriesResponse as PaginatedResponse).items; + final countries = countriesResponse.items; emit( state.copyWith( @@ -124,18 +124,26 @@ class CreateSourceBloc extends Bloc { emit(state.copyWith(status: CreateSourceStatus.submitting)); try { + final now = DateTime.now(); final newSource = Source( name: state.name, description: state.description.isNotEmpty ? state.description : null, url: state.url.isNotEmpty ? state.url : null, sourceType: state.sourceType, language: state.language.isNotEmpty ? state.language : null, + createdAt: now, + updatedAt: now, headquarters: state.headquarters, status: state.contentStatus, ); await _sourcesRepository.create(item: newSource); - emit(state.copyWith(status: CreateSourceStatus.success)); + emit( + state.copyWith( + status: CreateSourceStatus.success, + createdSource: newSource, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 8a6f837..1818cb5 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -32,6 +32,7 @@ final class CreateSourceState extends Equatable { this.countries = const [], this.contentStatus = ContentStatus.active, this.errorMessage, + this.createdSource, }); final CreateSourceStatus status; @@ -43,8 +44,8 @@ final class CreateSourceState extends Equatable { final Country? headquarters; final List countries; final ContentStatus contentStatus; - final String? errorMessage; + final Source? createdSource; /// Returns true if the form is valid and can be submitted. bool get isFormValid => name.isNotEmpty; @@ -59,8 +60,8 @@ final class CreateSourceState extends Equatable { ValueGetter? headquarters, List? countries, ContentStatus? contentStatus, - String? errorMessage, + Source? createdSource, }) { return CreateSourceState( status: status ?? this.status, @@ -72,8 +73,8 @@ final class CreateSourceState extends Equatable { headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, - errorMessage: errorMessage, + createdSource: createdSource ?? this.createdSource, ); } @@ -89,5 +90,6 @@ final class CreateSourceState extends Equatable { countries, contentStatus, errorMessage, + createdSource, ]; } 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 3f67aa0..04d08ca 100644 --- a/lib/content_management/bloc/edit_category/edit_category_bloc.dart +++ b/lib/content_management/bloc/edit_category/edit_category_bloc.dart @@ -135,13 +135,19 @@ class EditCategoryBloc extends Bloc { description: state.description.isNotEmpty ? state.description : null, iconUrl: state.iconUrl.isNotEmpty ? state.iconUrl : null, status: state.contentStatus, + updatedAt: DateTime.now(), ); await _categoriesRepository.update( id: _categoryId, item: updatedCategory, ); - emit(state.copyWith(status: EditCategoryStatus.success)); + emit( + state.copyWith( + status: EditCategoryStatus.success, + updatedCategory: updatedCategory, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 c38f0e2..0ec5343 100644 --- a/lib/content_management/bloc/edit_category/edit_category_state.dart +++ b/lib/content_management/bloc/edit_category/edit_category_state.dart @@ -28,6 +28,7 @@ final class EditCategoryState extends Equatable { this.iconUrl = '', this.contentStatus = ContentStatus.active, this.errorMessage, + this.updatedCategory, }); final EditCategoryStatus status; @@ -37,6 +38,7 @@ final class EditCategoryState extends Equatable { final String iconUrl; final ContentStatus contentStatus; final String? errorMessage; + final Category? updatedCategory; /// Returns true if the form is valid and can be submitted. bool get isFormValid => name.isNotEmpty; @@ -49,6 +51,7 @@ final class EditCategoryState extends Equatable { String? iconUrl, ContentStatus? contentStatus, String? errorMessage, + Category? updatedCategory, }) { return EditCategoryState( status: status ?? this.status, @@ -58,17 +61,19 @@ final class EditCategoryState extends Equatable { iconUrl: iconUrl ?? this.iconUrl, contentStatus: contentStatus ?? this.contentStatus, errorMessage: errorMessage ?? this.errorMessage, + updatedCategory: updatedCategory ?? this.updatedCategory, ); } @override List get props => [ - status, - initialCategory, - name, - description, - iconUrl, - contentStatus, - errorMessage, - ]; + status, + initialCategory, + name, + description, + iconUrl, + contentStatus, + errorMessage, + updatedCategory, + ]; } 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 b8f12f5..c2951e7 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -192,10 +192,16 @@ class EditHeadlineBloc extends Bloc { source: state.source, category: state.category, status: state.contentStatus, + updatedAt: DateTime.now(), ); await _headlinesRepository.update(id: _headlineId, item: updatedHeadline); - emit(state.copyWith(status: EditHeadlineStatus.success)); + emit( + state.copyWith( + status: EditHeadlineStatus.success, + updatedHeadline: updatedHeadline, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 5bbb617..5cd017f 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_state.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_state.dart @@ -33,6 +33,7 @@ final class EditHeadlineState extends Equatable { this.categories = const [], this.contentStatus = ContentStatus.active, this.errorMessage, + this.updatedHeadline, }); final EditHeadlineStatus status; @@ -47,6 +48,7 @@ final class EditHeadlineState extends Equatable { final List categories; final ContentStatus contentStatus; final String? errorMessage; + final Headline? updatedHeadline; /// Returns true if the form is valid and can be submitted. bool get isFormValid => title.isNotEmpty; @@ -64,6 +66,7 @@ final class EditHeadlineState extends Equatable { List? categories, ContentStatus? contentStatus, String? errorMessage, + Headline? updatedHeadline, }) { return EditHeadlineState( status: status ?? this.status, @@ -77,7 +80,8 @@ final class EditHeadlineState extends Equatable { sources: sources ?? this.sources, categories: categories ?? this.categories, contentStatus: contentStatus ?? this.contentStatus, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: errorMessage, + updatedHeadline: updatedHeadline ?? this.updatedHeadline, ); } @@ -95,5 +99,6 @@ final class EditHeadlineState extends Equatable { categories, contentStatus, errorMessage, + updatedHeadline, ]; } 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 73e7a3d..6da5cb8 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -3,9 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:ht_dashboard/l10n/app_localizations.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; -import 'package:ht_http_client/ht_http_client.dart'; import 'package:ht_shared/ht_shared.dart'; -import 'package:ht_dashboard/l10n/l10n.dart'; part 'edit_source_event.dart'; part 'edit_source_state.dart'; @@ -180,10 +178,16 @@ class EditSourceBloc extends Bloc { language: state.language.isNotEmpty ? state.language : null, headquarters: state.headquarters, status: state.contentStatus, + updatedAt: DateTime.now(), ); await _sourcesRepository.update(id: _sourceId, item: updatedSource); - emit(state.copyWith(status: EditSourceStatus.success)); + emit( + state.copyWith( + status: EditSourceStatus.success, + updatedSource: updatedSource, + ), + ); } on HtHttpException catch (e) { emit( state.copyWith( 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 8590885..52bd59b 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -32,6 +32,7 @@ final class EditSourceState extends Equatable { this.countries = const [], this.contentStatus = ContentStatus.active, this.errorMessage, + this.updatedSource, }); final EditSourceStatus status; @@ -45,6 +46,7 @@ final class EditSourceState extends Equatable { final List countries; final ContentStatus contentStatus; final String? errorMessage; + final Source? updatedSource; /// Returns true if the form is valid and can be submitted. bool get isFormValid => name.isNotEmpty; @@ -61,6 +63,7 @@ final class EditSourceState extends Equatable { List? countries, ContentStatus? contentStatus, String? errorMessage, + Source? updatedSource, }) { return EditSourceState( status: status ?? this.status, @@ -73,7 +76,8 @@ final class EditSourceState extends Equatable { headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: errorMessage, + updatedSource: updatedSource ?? this.updatedSource, ); } @@ -90,5 +94,6 @@ final class EditSourceState extends Equatable { countries, contentStatus, errorMessage, + updatedSource, ]; } diff --git a/lib/content_management/view/categories_page.dart b/lib/content_management/view/categories_page.dart index b0b9b69..13cf504 100644 --- a/lib/content_management/view/categories_page.dart +++ b/lib/content_management/view/categories_page.dart @@ -7,12 +7,9 @@ import 'package:ht_dashboard/l10n/app_localizations.dart'; 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'; -import 'package:ht_dashboard/shared/extensions/content_status_l10n.dart'; +import 'package:intl/intl.dart'; /// {@template categories_page} /// A page for displaying and managing Categories in a tabular format. @@ -88,7 +85,8 @@ class _CategoriesPageState extends State { source: _CategoriesDataSource( context: context, categories: state.categories, - isLoading: state.categoriesStatus == ContentManagementStatus.loading, + isLoading: + state.categoriesStatus == ContentManagementStatus.loading, hasMore: state.categoriesHasMore, l10n: l10n, ), @@ -156,13 +154,23 @@ class _CategoriesDataSource extends DataTableSource { return DataRow2( onSelectChanged: (selected) { if (selected ?? false) { - context.goNamed(Routes.editCategoryName, pathParameters: {'id': category.id}); + context.goNamed( + Routes.editCategoryName, + pathParameters: {'id': category.id}, + ); } }, cells: [ DataCell(Text(category.name)), DataCell(Text(category.status.l10n(context))), - DataCell(Text(category.updatedAt?.toLocal().toString() ?? l10n.notAvailable)), + DataCell( + Text( + category.updatedAt != null + // TODO(fulleni): Make date format configurable by admin. + ? DateFormat('dd-MM-yyyy').format(category.updatedAt!.toLocal()) + : l10n.notAvailable, + ), + ), DataCell( Row( children: [ @@ -203,7 +211,9 @@ class _CategoriesDataSource 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 ? categories.length + 1 : categories.length + kDefaultRowsPerPage; + return isLoading + ? categories.length + 1 + : categories.length + kDefaultRowsPerPage; } return categories.length; } diff --git a/lib/content_management/view/content_management_page.dart b/lib/content_management/view/content_management_page.dart index 3f9749f..d4f0305 100644 --- a/lib/content_management/view/content_management_page.dart +++ b/lib/content_management/view/content_management_page.dart @@ -96,8 +96,10 @@ class _ContentManagementPageState extends State icon: const Icon(Icons.add), tooltip: 'Add New Item', // Consider localizing this tooltip onPressed: () { - final currentTab = - context.read().state.activeTab; + final currentTab = context + .read() + .state + .activeTab; switch (currentTab) { case ContentManagementTab.headlines: context.goNamed(Routes.createHeadlineName); diff --git a/lib/content_management/view/create_category_page.dart b/lib/content_management/view/create_category_page.dart index 470ca99..dc043a2 100644 --- a/lib/content_management/view/create_category_page.dart +++ b/lib/content_management/view/create_category_page.dart @@ -4,8 +4,6 @@ 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_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'; @@ -75,18 +73,15 @@ class _CreateCategoryViewState extends State<_CreateCategoryView> { listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.status == CreateCategoryStatus.success && + state.createdCategory != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( - SnackBar( - content: Text(l10n.categoryCreatedSuccessfully), - ), + SnackBar(content: Text(l10n.categoryCreatedSuccessfully)), ); context.read().add( - const LoadCategoriesRequested( - limit: kDefaultRowsPerPage, - ), + CategoryAdded(state.createdCategory!), ); context.pop(); } @@ -158,9 +153,9 @@ class _CreateCategoryViewState extends State<_CreateCategoryView> { }).toList(), onChanged: (value) { if (value == null) return; - context - .read() - .add(CreateCategoryStatusChanged(value)); + context.read().add( + CreateCategoryStatusChanged(value), + ); }, ), ], diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index 06a79b7..075d2a4 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -3,9 +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'; import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -77,18 +75,15 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.status == CreateHeadlineStatus.success && + state.createdHeadline != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( - SnackBar( - content: Text(l10n.headlineCreatedSuccessfully), - ), + SnackBar(content: Text(l10n.headlineCreatedSuccessfully)), ); context.read().add( - const LoadHeadlinesRequested( - limit: kDefaultRowsPerPage, - ), + HeadlineAdded(state.createdHeadline!), ); context.pop(); } diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index b91c522..652b689 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -4,9 +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'; import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -77,6 +75,7 @@ class _CreateSourceViewState extends State<_CreateSourceView> { listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.status == CreateSourceStatus.success && + state.createdSource != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() @@ -84,9 +83,7 @@ class _CreateSourceViewState extends State<_CreateSourceView> { SnackBar(content: Text(l10n.sourceCreatedSuccessfully)), ); context.read().add( - const LoadSourcesRequested( - limit: kDefaultRowsPerPage, - ), + SourceAdded(state.createdSource!), ); context.pop(); } diff --git a/lib/content_management/view/edit_category_page.dart b/lib/content_management/view/edit_category_page.dart index 42b7184..2a88896 100644 --- a/lib/content_management/view/edit_category_page.dart +++ b/lib/content_management/view/edit_category_page.dart @@ -4,8 +4,6 @@ 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'; import 'package:ht_shared/ht_shared.dart'; @@ -101,7 +99,7 @@ class _EditCategoryViewState extends State<_EditCategoryView> { previous.initialCategory != current.initialCategory, listener: (context, state) { if (state.status == EditCategoryStatus.success && - state.initialCategory != null && + state.updatedCategory != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() @@ -110,9 +108,7 @@ class _EditCategoryViewState extends State<_EditCategoryView> { const SnackBar(content: Text('Category updated successfully.')), ); context.read().add( - const LoadCategoriesRequested( - limit: kDefaultRowsPerPage, - ), + CategoryUpdated(state.updatedCategory!), ); context.pop(); } diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index 7f1215b..3f90c5b 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -3,9 +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'; import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -106,7 +104,7 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { previous.initialHeadline != current.initialHeadline, listener: (context, state) { if (state.status == EditHeadlineStatus.success && - state.initialHeadline != null && + state.updatedHeadline != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() @@ -116,9 +114,7 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { ), ); context.read().add( - const LoadHeadlinesRequested( - limit: kDefaultRowsPerPage, - ), + HeadlineUpdated(state.updatedHeadline!), ); context.pop(); } diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index 738e9d4..469bbd5 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -4,8 +4,6 @@ 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'; import 'package:ht_shared/ht_shared.dart'; @@ -105,7 +103,7 @@ class _EditSourceViewState extends State<_EditSourceView> { previous.initialSource != current.initialSource, listener: (context, state) { if (state.status == EditSourceStatus.success && - state.initialSource != null && + state.updatedSource != null && ModalRoute.of(context)!.isCurrent) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() @@ -113,9 +111,7 @@ class _EditSourceViewState extends State<_EditSourceView> { SnackBar(content: Text(l10n.sourceUpdatedSuccessfully)), ); context.read().add( - const LoadSourcesRequested( - limit: kDefaultRowsPerPage, - ), + SourceUpdated(state.updatedSource!), ); context.pop(); } diff --git a/lib/content_management/view/headlines_page.dart b/lib/content_management/view/headlines_page.dart index 5dcd47f..55beb51 100644 --- a/lib/content_management/view/headlines_page.dart +++ b/lib/content_management/view/headlines_page.dart @@ -7,11 +7,12 @@ 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/constants/pagination_constants.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'; -import 'package:ht_dashboard/shared/constants/pagination_constants.dart'; +import 'package:intl/intl.dart'; /// {@template headlines_page} /// A page for displaying and managing Headlines in a tabular format. @@ -171,7 +172,12 @@ class _HeadlinesDataSource extends DataTableSource { DataCell(Text(headline.source?.name ?? l10n.unknown)), DataCell(Text(headline.status.l10n(context))), DataCell( - Text(headline.updatedAt?.toLocal().toString() ?? l10n.notAvailable), + Text( + headline.updatedAt != null + // TODO(fulleni): Make date format configurable by admin. + ? DateFormat('dd-MM-yyyy').format(headline.updatedAt!.toLocal()) + : l10n.notAvailable, + ), ), DataCell( Row( diff --git a/lib/content_management/view/sources_page.dart b/lib/content_management/view/sources_page.dart index ace07df..2cbf184 100644 --- a/lib/content_management/view/sources_page.dart +++ b/lib/content_management/view/sources_page.dart @@ -5,14 +5,15 @@ 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'; import 'package:ht_dashboard/shared/constants/app_spacing.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/widgets/failure_state_widget.dart'; import 'package:ht_dashboard/shared/widgets/loading_state_widget.dart'; import 'package:ht_shared/ht_shared.dart'; +import 'package:intl/intl.dart'; /// {@template sources_page} /// A page for displaying and managing Sources in a tabular format. @@ -170,7 +171,12 @@ class _SourcesDataSource extends DataTableSource { DataCell(Text(source.sourceType?.localizedName(l10n) ?? l10n.unknown)), DataCell(Text(source.status.l10n(context))), DataCell( - Text(source.updatedAt?.toLocal().toString() ?? l10n.notAvailable), + Text( + source.updatedAt != null + // TODO(fulleni): Make date format configurable by admin. + ? DateFormat('dd-MM-yyyy').format(source.updatedAt!.toLocal()) + : l10n.notAvailable, + ), ), DataCell( Row( diff --git a/lib/shared/extensions/content_status_l10n.dart b/lib/shared/extensions/content_status_l10n.dart index c2f9b6c..3e52c83 100644 --- a/lib/shared/extensions/content_status_l10n.dart +++ b/lib/shared/extensions/content_status_l10n.dart @@ -16,4 +16,4 @@ extension ContentStatusL10n on ContentStatus { return l10n.contentStatusDraft; } } -} \ No newline at end of file +}