-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance content management #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
351a651
refactor(content_management): rename deletion events to archiving
fulleni c3c0fb6
feat(content_management): implement archive functionality for headlin…
fulleni 1013dc5
feat(content_management): replace delete functionality with archive
fulleni e76dc71
refactor(content_management): replace delete functionality with archive
fulleni 632be8f
refactor(content_management): replace delete action with archive
fulleni 7c1dd74
fix(content_management): allow text overflow for headline title
fulleni f8dd350
refactor(content_management): improve topics page UI and code consist…
fulleni 08af2ed
fix(content_management): allow source name to overflow with an ellipsis
fulleni 2853886
fix(content_management): improve headline pagination and UI
fulleni 2fcfb21
fix(content_management): remove loading row from topics table
fulleni c800880
refactor(content_management): improve sources table loading state
fulleni 0db0076
feat(router): add archived content page route
fulleni a83aa11
feat(content_management): add archived items button and update tooltips
fulleni 0c43dc8
feat(content_management): add archived content page
fulleni 1a164cd
feat(router): add routes for archived headlines, topics, and sources
fulleni 392cb69
feat(content_management): add archived content state
fulleni 6c723b6
feat(content_management): add archived content events
fulleni b00cf36
feat(archived_content): implement ArchivedContentBloc
fulleni 7b8fae0
refactor(content_management): remove unused archived content bloc
fulleni 225c29f
feat(content_management): add archived headlines bloc
fulleni 439d5e0
feat(content_management): add archived topics bloc
fulleni 5a06895
feat(content_management): add archived sources bloc
fulleni ac2bd91
refactor(content_management): remove archived content page
fulleni 1aee2f4
feat(content_management): navigate to respective archived pages
fulleni 69fddcd
feat(router): add archived content routes and placeholders
fulleni deb091e
feat(content_management): add archived headlines events
fulleni c8523f2
refactor(content_management): enhance archived headlines state manage…
fulleni 769861f
build(content_management): add core dependency
fulleni 7d5272d
feat(content_management): implement loading, restoring, and deleting …
fulleni 7a238be
feat(content_management): add ArchivedHeadlinesPage with data table a…
fulleni 8bc91cf
feat(localization): add Arabic and English translations for archived …
fulleni a67b062
refactor(content_management): update props type in ArchivedTopicsEven…
fulleni 1cf3e96
refactor(content_management): restructure ArchivedTopicsState to incl…
fulleni 9772698
feat(content_management): enhance ArchivedTopicsBloc with pagination …
fulleni 97f5c36
feat(content_management): add ArchivedTopicsPage with pagination and …
fulleni daa8060
fix(archived_headlines): add missing import for AppLocalizations
fulleni 5059e8a
refactor(archived_sources): update props type in ArchivedSourcesEvent…
fulleni ca80750
refactor(archived_sources): redefine ArchivedSourcesState with status…
fulleni 7f0eb5f
refactor(archived_sources_bloc): improve error handling in source res…
fulleni da59c9c
chore: misc
fulleni 13f519e
chore: misc
fulleni 2799f65
feat(localization): add archived topics and sources translations
fulleni 72ebd21
fix(localization): remove TODO comments for localization in archived …
fulleni 66223fe
fix(localization): replace hardcoded strings with localized values in…
fulleni 95536d2
fix(localization): replace hardcoded tooltips with localized values i…
fulleni 1f3c83c
fix(localization): replace hardcoded tooltip with localized value in …
fulleni 1570776
fix(localization): replace hardcoded tooltip with localized value in …
fulleni 600be46
fix(localization): replace hardcoded tooltip with localized value for…
fulleni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
118 changes: 118 additions & 0 deletions
118
lib/content_management/bloc/archived_headlines/archived_headlines_bloc.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import 'package:bloc/bloc.dart'; | ||
import 'package:core/core.dart'; | ||
import 'package:data_repository/data_repository.dart'; | ||
import 'package:equatable/equatable.dart'; | ||
|
||
part 'archived_headlines_event.dart'; | ||
part 'archived_headlines_state.dart'; | ||
|
||
class ArchivedHeadlinesBloc | ||
extends Bloc<ArchivedHeadlinesEvent, ArchivedHeadlinesState> { | ||
ArchivedHeadlinesBloc({ | ||
required DataRepository<Headline> headlinesRepository, | ||
}) : _headlinesRepository = headlinesRepository, | ||
super(const ArchivedHeadlinesState()) { | ||
on<LoadArchivedHeadlinesRequested>(_onLoadArchivedHeadlinesRequested); | ||
on<RestoreHeadlineRequested>(_onRestoreHeadlineRequested); | ||
on<DeleteHeadlineForeverRequested>(_onDeleteHeadlineForeverRequested); | ||
} | ||
|
||
final DataRepository<Headline> _headlinesRepository; | ||
|
||
Future<void> _onLoadArchivedHeadlinesRequested( | ||
LoadArchivedHeadlinesRequested event, | ||
Emitter<ArchivedHeadlinesState> emit, | ||
) async { | ||
emit(state.copyWith(status: ArchivedHeadlinesStatus.loading)); | ||
try { | ||
final isPaginating = event.startAfterId != null; | ||
final previousHeadlines = isPaginating ? state.headlines : <Headline>[]; | ||
|
||
final paginatedHeadlines = await _headlinesRepository.readAll( | ||
filter: {'status': ContentStatus.archived.name}, | ||
sort: [const SortOption('updatedAt', SortOrder.desc)], | ||
pagination: PaginationOptions( | ||
cursor: event.startAfterId, | ||
limit: event.limit, | ||
), | ||
); | ||
emit( | ||
state.copyWith( | ||
status: ArchivedHeadlinesStatus.success, | ||
headlines: [...previousHeadlines, ...paginatedHeadlines.items], | ||
cursor: paginatedHeadlines.cursor, | ||
hasMore: paginatedHeadlines.hasMore, | ||
), | ||
); | ||
} on HttpException catch (e) { | ||
emit( | ||
state.copyWith( | ||
status: ArchivedHeadlinesStatus.failure, | ||
exception: e, | ||
), | ||
); | ||
} catch (e) { | ||
emit( | ||
state.copyWith( | ||
status: ArchivedHeadlinesStatus.failure, | ||
exception: UnknownException('An unexpected error occurred: $e'), | ||
), | ||
); | ||
} | ||
} | ||
|
||
Future<void> _onRestoreHeadlineRequested( | ||
RestoreHeadlineRequested event, | ||
Emitter<ArchivedHeadlinesState> emit, | ||
) async { | ||
final originalHeadlines = List<Headline>.from(state.headlines); | ||
final headlineIndex = originalHeadlines.indexWhere((h) => h.id == event.id); | ||
if (headlineIndex == -1) return; | ||
|
||
final headlineToRestore = originalHeadlines[headlineIndex]; | ||
final updatedHeadlines = originalHeadlines..removeAt(headlineIndex); | ||
|
||
emit(state.copyWith(headlines: updatedHeadlines)); | ||
|
||
try { | ||
await _headlinesRepository.update( | ||
id: event.id, | ||
item: headlineToRestore.copyWith(status: ContentStatus.active), | ||
); | ||
} on HttpException catch (e) { | ||
emit(state.copyWith(headlines: originalHeadlines, exception: e)); | ||
} catch (e) { | ||
emit( | ||
state.copyWith( | ||
headlines: originalHeadlines, | ||
exception: UnknownException('An unexpected error occurred: $e'), | ||
), | ||
); | ||
} | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
Future<void> _onDeleteHeadlineForeverRequested( | ||
DeleteHeadlineForeverRequested event, | ||
Emitter<ArchivedHeadlinesState> emit, | ||
) async { | ||
final originalHeadlines = List<Headline>.from(state.headlines); | ||
final headlineIndex = originalHeadlines.indexWhere((h) => h.id == event.id); | ||
if (headlineIndex == -1) return; | ||
|
||
final updatedHeadlines = originalHeadlines..removeAt(headlineIndex); | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
emit(state.copyWith(headlines: updatedHeadlines)); | ||
|
||
try { | ||
await _headlinesRepository.delete(id: event.id); | ||
} on HttpException catch (e) { | ||
emit(state.copyWith(headlines: originalHeadlines, exception: e)); | ||
} catch (e) { | ||
emit( | ||
state.copyWith( | ||
headlines: originalHeadlines, | ||
exception: UnknownException('An unexpected error occurred: $e'), | ||
), | ||
); | ||
} | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
lib/content_management/bloc/archived_headlines/archived_headlines_event.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
part of 'archived_headlines_bloc.dart'; | ||
|
||
sealed class ArchivedHeadlinesEvent extends Equatable { | ||
const ArchivedHeadlinesEvent(); | ||
|
||
@override | ||
List<Object?> get props => []; | ||
} | ||
|
||
/// Event to request loading of archived headlines. | ||
final class LoadArchivedHeadlinesRequested extends ArchivedHeadlinesEvent { | ||
const LoadArchivedHeadlinesRequested({this.startAfterId, this.limit}); | ||
|
||
final String? startAfterId; | ||
final int? limit; | ||
|
||
@override | ||
List<Object?> get props => [startAfterId, limit]; | ||
} | ||
|
||
/// Event to restore an archived headline. | ||
final class RestoreHeadlineRequested extends ArchivedHeadlinesEvent { | ||
const RestoreHeadlineRequested(this.id); | ||
|
||
final String id; | ||
|
||
@override | ||
List<Object?> get props => [id]; | ||
} | ||
|
||
/// Event to permanently delete an archived headline. | ||
final class DeleteHeadlineForeverRequested extends ArchivedHeadlinesEvent { | ||
const DeleteHeadlineForeverRequested(this.id); | ||
|
||
final String id; | ||
|
||
@override | ||
List<Object?> get props => [id]; | ||
} |
51 changes: 51 additions & 0 deletions
51
lib/content_management/bloc/archived_headlines/archived_headlines_state.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
part of 'archived_headlines_bloc.dart'; | ||
|
||
/// Represents the status of archived content operations. | ||
enum ArchivedHeadlinesStatus { | ||
initial, | ||
loading, | ||
success, | ||
failure, | ||
} | ||
|
||
/// The state for the archived content feature. | ||
class ArchivedHeadlinesState extends Equatable { | ||
const ArchivedHeadlinesState({ | ||
this.status = ArchivedHeadlinesStatus.initial, | ||
this.headlines = const [], | ||
this.cursor, | ||
this.hasMore = false, | ||
this.exception, | ||
}); | ||
|
||
final ArchivedHeadlinesStatus status; | ||
final List<Headline> headlines; | ||
final String? cursor; | ||
final bool hasMore; | ||
final HttpException? exception; | ||
|
||
ArchivedHeadlinesState copyWith({ | ||
ArchivedHeadlinesStatus? status, | ||
List<Headline>? headlines, | ||
String? cursor, | ||
bool? hasMore, | ||
HttpException? exception, | ||
}) { | ||
return ArchivedHeadlinesState( | ||
status: status ?? this.status, | ||
headlines: headlines ?? this.headlines, | ||
cursor: cursor ?? this.cursor, | ||
hasMore: hasMore ?? this.hasMore, | ||
exception: exception ?? this.exception, | ||
); | ||
} | ||
|
||
@override | ||
List<Object?> get props => [ | ||
status, | ||
headlines, | ||
cursor, | ||
hasMore, | ||
exception, | ||
]; | ||
} |
92 changes: 92 additions & 0 deletions
92
lib/content_management/bloc/archived_sources/archived_sources_bloc.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import 'package:bloc/bloc.dart'; | ||
import 'package:core/core.dart'; | ||
import 'package:data_repository/data_repository.dart'; | ||
import 'package:equatable/equatable.dart'; | ||
|
||
part 'archived_sources_event.dart'; | ||
part 'archived_sources_state.dart'; | ||
|
||
class ArchivedSourcesBloc | ||
extends Bloc<ArchivedSourcesEvent, ArchivedSourcesState> { | ||
ArchivedSourcesBloc({ | ||
required DataRepository<Source> sourcesRepository, | ||
}) : _sourcesRepository = sourcesRepository, | ||
super(const ArchivedSourcesState()) { | ||
on<LoadArchivedSourcesRequested>(_onLoadArchivedSourcesRequested); | ||
on<RestoreSourceRequested>(_onRestoreSourceRequested); | ||
} | ||
|
||
final DataRepository<Source> _sourcesRepository; | ||
|
||
Future<void> _onLoadArchivedSourcesRequested( | ||
LoadArchivedSourcesRequested event, | ||
Emitter<ArchivedSourcesState> emit, | ||
) async { | ||
emit(state.copyWith(status: ArchivedSourcesStatus.loading)); | ||
try { | ||
final isPaginating = event.startAfterId != null; | ||
final previousSources = isPaginating ? state.sources : <Source>[]; | ||
|
||
final paginatedSources = await _sourcesRepository.readAll( | ||
filter: {'status': ContentStatus.archived.name}, | ||
sort: [const SortOption('updatedAt', SortOrder.desc)], | ||
pagination: PaginationOptions( | ||
cursor: event.startAfterId, | ||
limit: event.limit, | ||
), | ||
); | ||
emit( | ||
state.copyWith( | ||
status: ArchivedSourcesStatus.success, | ||
sources: [...previousSources, ...paginatedSources.items], | ||
cursor: paginatedSources.cursor, | ||
hasMore: paginatedSources.hasMore, | ||
), | ||
); | ||
} on HttpException catch (e) { | ||
emit( | ||
state.copyWith( | ||
status: ArchivedSourcesStatus.failure, | ||
exception: e, | ||
), | ||
); | ||
} catch (e) { | ||
emit( | ||
state.copyWith( | ||
status: ArchivedSourcesStatus.failure, | ||
exception: UnknownException('An unexpected error occurred: $e'), | ||
), | ||
); | ||
} | ||
} | ||
|
||
Future<void> _onRestoreSourceRequested( | ||
RestoreSourceRequested event, | ||
Emitter<ArchivedSourcesState> emit, | ||
) async { | ||
final originalSources = List<Source>.from(state.sources); | ||
final sourceIndex = originalSources.indexWhere((s) => s.id == event.id); | ||
if (sourceIndex == -1) return; | ||
|
||
final sourceToRestore = originalSources[sourceIndex]; | ||
final updatedSources = originalSources..removeAt(sourceIndex); | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
emit(state.copyWith(sources: updatedSources)); | ||
|
||
try { | ||
await _sourcesRepository.update( | ||
id: event.id, | ||
item: sourceToRestore.copyWith(status: ContentStatus.active), | ||
); | ||
} on HttpException catch (e) { | ||
emit(state.copyWith(sources: originalSources, exception: e)); | ||
} catch (e) { | ||
emit( | ||
state.copyWith( | ||
sources: originalSources, | ||
exception: UnknownException('An unexpected error occurred: $e'), | ||
), | ||
); | ||
} | ||
fulleni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
lib/content_management/bloc/archived_sources/archived_sources_event.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
part of 'archived_sources_bloc.dart'; | ||
|
||
sealed class ArchivedSourcesEvent extends Equatable { | ||
const ArchivedSourcesEvent(); | ||
|
||
@override | ||
List<Object?> get props => []; | ||
} | ||
|
||
/// Event to request loading of archived sources. | ||
final class LoadArchivedSourcesRequested extends ArchivedSourcesEvent { | ||
const LoadArchivedSourcesRequested({this.startAfterId, this.limit}); | ||
|
||
final String? startAfterId; | ||
final int? limit; | ||
|
||
@override | ||
List<Object?> get props => [startAfterId, limit]; | ||
} | ||
|
||
/// Event to restore an archived source. | ||
final class RestoreSourceRequested extends ArchivedSourcesEvent { | ||
const RestoreSourceRequested(this.id); | ||
|
||
final String id; | ||
|
||
@override | ||
List<Object?> get props => [id]; | ||
} |
51 changes: 51 additions & 0 deletions
51
lib/content_management/bloc/archived_sources/archived_sources_state.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
part of 'archived_sources_bloc.dart'; | ||
|
||
/// Represents the status of archived content operations. | ||
enum ArchivedSourcesStatus { | ||
initial, | ||
loading, | ||
success, | ||
failure, | ||
} | ||
|
||
/// The state for the archived content feature. | ||
class ArchivedSourcesState extends Equatable { | ||
const ArchivedSourcesState({ | ||
this.status = ArchivedSourcesStatus.initial, | ||
this.sources = const [], | ||
this.cursor, | ||
this.hasMore = false, | ||
this.exception, | ||
}); | ||
|
||
final ArchivedSourcesStatus status; | ||
final List<Source> sources; | ||
final String? cursor; | ||
final bool hasMore; | ||
final HttpException? exception; | ||
|
||
ArchivedSourcesState copyWith({ | ||
ArchivedSourcesStatus? status, | ||
List<Source>? sources, | ||
String? cursor, | ||
bool? hasMore, | ||
HttpException? exception, | ||
}) { | ||
return ArchivedSourcesState( | ||
status: status ?? this.status, | ||
sources: sources ?? this.sources, | ||
cursor: cursor ?? this.cursor, | ||
hasMore: hasMore ?? this.hasMore, | ||
exception: exception ?? this.exception, | ||
); | ||
} | ||
|
||
@override | ||
List<Object?> get props => [ | ||
status, | ||
sources, | ||
cursor, | ||
hasMore, | ||
exception, | ||
]; | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.