Skip to content

Navigation Menu

Sign in
Appearance settings

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

Provide feedback

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

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Implementing a robust drafting feature for headline content management #80

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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
4cdabcb
feat(l10n): add Arabic and English translations for draft management ...
fulleni Sep 22, 2025
5e754b2
feat(router): add draft headlines route and page
fulleni Sep 22, 2025
6ca7758
feat(content_management): add draft headlines management state fields
fulleni Sep 22, 2025
00185bf
feat(content_management): add drafts tab and implement load draft hea...
fulleni Sep 22, 2025
21bac98
feat(content_management): add draft saving and publishing functionali...
fulleni Sep 22, 2025
dce3f6b
feat(content_management): add events for saving and publishing headlines
fulleni Sep 22, 2025
d949270
feat(content_management): add save as draft and publish functionality
fulleni Sep 22, 2025
14dc5e0
feat(content_management): add events for saving and publishing headlines
fulleni Sep 22, 2025
06a2c4a
feat(content_management): enhance headline editing functionality
fulleni Sep 22, 2025
16908d9
feat(content_management): add draft headlines functionality
fulleni Sep 22, 2025
95e55a1
feat(content_management): enhance create headline functionality
fulleni Sep 22, 2025
ac06267
feat(content_management): add draft headlines page
fulleni Sep 22, 2025
c329225
feat(content_management): implement DraftHeadlinesState for BLoC
fulleni Sep 22, 2025
992141d
feat(content_management): add draft headlines events
fulleni Sep 22, 2025
8ab8747
feat(content_management): implement DraftHeadlinesBloc
fulleni Sep 22, 2025
06d0bdf
fix(shared): ensure initial selected item is included in selection page
fulleni Sep 22, 2025
5be6b1f
feat(searchable_selection_input): add includeInactiveSelectedItem option
fulleni Sep 22, 2025
6f6d2b3
feat(SelectionPageArguments): add includeInactiveSelectedItem parameter
fulleni Sep 22, 2025
16318d5
fix(content_management): remove outdated comments and clean up code
fulleni Sep 22, 2025
2c05ab3
feat(l10n): add Arabic and English translations for content drafts
fulleni Sep 22, 2025
405c0ce
refactor(content_management): remove draft headlines feature
fulleni Sep 22, 2025
8a24842
refactor(content_management): remove draft-related properties from Co...
fulleni Sep 22, 2025
eb26a64
refactor(content_management): relocate draft items to the main page
fulleni Sep 22, 2025
7b759a8
refactor(content_management): reimplement DraftHeadlinesPage as a Sta...
fulleni Sep 22, 2025
8afce44
feat(content_management): add draft sources page placeholder
fulleni Sep 22, 2025
8002118
feat(content_management): add draft topics page placeholder
fulleni Sep 22, 2025
50e799d
feat(router): add routes for draft topics and sources
fulleni Sep 22, 2025
76fad1d
feat(router): add routes for draft sources and topics
fulleni Sep 22, 2025
3b49f58
refactor(content_management): remove contentStatus from CreateHeadlin...
fulleni Sep 22, 2025
a9099c1
refactor(content_management): remove unused events
fulleni Sep 22, 2025
29c8113
refactor(create-headline): remove unused code and improve draft savin...
fulleni Sep 22, 2025
3ab6b3d
refactor(create-source): remove unused contentStatus field
fulleni Sep 22, 2025
a352ccd
refactor(content_management): add create source draft and publish events
fulleni Sep 22, 2025
09fd5ac
feat(content_management): implement save as draft and publish source
fulleni Sep 22, 2025
51f5ac5
feat(content_management): implement save as draft and publish topic f...
fulleni Sep 22, 2025
e2c2981
refactor(content_management): replace status change event with publis...
fulleni Sep 22, 2025
fe9db62
refactor(content_management): remove unused contentStatus from Create...
fulleni Sep 22, 2025
ed6853f
refactor(content_management): remove unused event handlers and update...
fulleni Sep 22, 2025
57dc1af
refactor(content_management): remove unused events
fulleni Sep 22, 2025
598bbdd
refactor(content_management): remove contentStatus from EditHeadlineS...
fulleni Sep 22, 2025
1b7e67f
refactor(content_management): separate save as draft and publish func...
fulleni Sep 22, 2025
06a93c6
refactor(content_management): update edit source events
fulleni Sep 22, 2025
85b59be
refactor(content_management): remove unused contentStatus field
fulleni Sep 22, 2025
a2d2c57
refactor(content_management): update EditTopicBloc for topic draft sa...
fulleni Sep 22, 2025
08f6ab7
refactor(content_management): update edit topic events
fulleni Sep 22, 2025
29913ac
refactor(content_management): simplify EditTopicState
fulleni Sep 22, 2025
e002928
refactor(content_management): remove unused import
fulleni Sep 22, 2025
d59e333
refactor(content_management): replace status input with save options ...
fulleni Sep 22, 2025
16e129e
feat(content_management): implement save options and form validation ...
fulleni Sep 22, 2025
f89ba90
refactor(content_management): improve create topic flow
fulleni Sep 22, 2025
c9e8035
refactor(content_management): replace status input with save options ...
fulleni Sep 22, 2025
8413fe9
refactor(content_management): improve save functionality and remove s...
fulleni Sep 22, 2025
ce20f47
refactor(content_management): improve topic save functionality
fulleni Sep 22, 2025
95617e2
feat(l10n): add translations for save dialog
fulleni Sep 22, 2025
17fef27
refactor(searchable_selection_input): pass includeInactiveSelectedIte...
fulleni Sep 22, 2025
8a72807
fix(shared): apply default active status filter in searchable selection
fulleni Sep 22, 2025
9c19aa2
feat(l10n): add Arabic and English translations for draft topic and s...
fulleni Sep 22, 2025
a656310
feat(content_management): implement DraftSourcesState for BLoC
fulleni Sep 22, 2025
b2a8fad
feat(content_management): add draft sources events
fulleni Sep 22, 2025
a905f81
feat(content_management): implement DraftSourcesBloc for source draft...
fulleni Sep 22, 2025
d947ac1
feat(content_management): implement DraftTopicsState for BLoC
fulleni Sep 22, 2025
19e0a67
feat(content_management): add draft topics bloc events
fulleni Sep 22, 2025
d2b935a
feat(bloc): implement DraftTopicsBloc for topic management
fulleni Sep 22, 2025
4c1623d
refactor(content_management): remove unnecessary comments
fulleni Sep 22, 2025
8248921
feat(content_management): implement draft sources page
fulleni Sep 22, 2025
c7ec00a
feat(content_management): implement draft topics page functionality
fulleni Sep 22, 2025
1a48a18
refactor(content_management): remove form validity checks
fulleni Sep 22, 2025
53bfac6
refactor(content_management): remove form validity checks
fulleni Sep 22, 2025
71d58f1
refactor(content_management): remove form validity checks before savi...
fulleni Sep 22, 2025
104fc6b
refactor(content_management): remove form validity check
fulleni Sep 22, 2025
9a491f0
refactor(content_management): remove form validity checks before savi...
fulleni Sep 22, 2025
724565c
refactor(content_management): remove form validity checks before savi...
fulleni Sep 22, 2025
fa2f2d9
refactor(content_management): remove unused dialog and improve save b...
fulleni Sep 22, 2025
51de49c
refactor(create-source): remove unused dialog and simplify save butto...
fulleni Sep 22, 2025
548b768
refactor(content_management): remove unused dialog and simplify save ...
fulleni Sep 22, 2025
aed6b9b
refactor(content_management): simplify headline editing and remove pr...
fulleni Sep 22, 2025
3a8bd24
refactor(content_management): simplify source editing save functionality
fulleni Sep 22, 2025
fccfaa0
refactor(content_management): simplify edit topic save functionality
fulleni Sep 22, 2025
7342b6c
refactor(bloc_observer): simplify state change logging
fulleni Sep 22, 2025
590cdd3
docs(README): update content management descriptions
fulleni Sep 22, 2025
b592ea6
style(content_management): update action button icons
fulleni Sep 22, 2025
a61b70e
refactor(content_management): improve list handling in DraftHeadlines...
fulleni Sep 22, 2025
b515a80
fix(content_management): optimize source publishing process
fulleni Sep 22, 2025
8a7503c
perf(content_management): optimize topic list handling
fulleni Sep 22, 2025
bb2a71e
fix(content_management): handle null values in DraftHeadlinesState
fulleni Sep 22, 2025
56870d5
fix(content_management): handle null values in DraftSourcesState
fulleni Sep 22, 2025
c2da7f8
fix(content_management): preserve snackbar data across state changes
fulleni Sep 22, 2025
c515c91
refactor(content_management): replace FailureStateWidget with SnackBa...
fulleni Sep 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ Click on any category to explore.

### πŸ“° Comprehensive Content Management
Manage the entire lifecycle of your news content with full CRUD (Create, Read, Update, Delete) capabilities, complemented by advanced archiving and restoration features:
- **Headlines:** Create, edit, publish, archive, restore, and permanently delete news articles.
- **Topics:** Organize, define, archive, and restore news topics.
- **Sources:** Maintain, update, archive, and restore news sources.
> **πŸ’‘ Your Advantage:** Gain detailed control over your content. This centralized system ensures accuracy and consistency, allowing you to manage active content and easily retrieve or remove archived items.
- **Headlines:** Draft, Create, edit, publish, archive, restore, and permanently delete news articles.
- **Topics:** Draft, Create, edit, archive, and restore news topics.
- **Sources:** Draft, Create, edit, archive, and restore news sources.
> **πŸ’‘ Your Advantage:** Gain detailed control over your content. This centralized system ensures accuracy and consistency, allowing you to manage draft/active content and easily retrieve or remove archived items.

---

Expand Down
33 changes: 5 additions & 28 deletions lib/bloc_observer.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,16 @@ class AppBlocObserver extends BlocObserver {

// Initialize state information strings.
// By default, truncate the full string representation of the state
// to the first 250 characters to prevent excessively long logs.
var oldStateInfo = oldState.toString().substring(
// to the first 150 characters to prevent excessively long logs.
final oldStateInfo = oldState.toString().substring(
0,
oldState.toString().length > 250 ? 250 : oldState.toString().length,
oldState.toString().length > 150 ? 150 : oldState.toString().length,
);
var newStateInfo = newState.toString().substring(
final newStateInfo = newState.toString().substring(
0,
newState.toString().length > 250 ? 250 : newState.toString().length,
newState.toString().length > 150 ? 150 : newState.toString().length,
);

try {
// Attempt to access a 'status' property on the state objects.
// Many BLoC states use a 'status' property (e.g., Loading, Success, Failure)
// to represent their current lifecycle phase. If this property exists
// and is not null, prioritize logging its value for conciseness.
if (oldState.status != null) {
oldStateInfo = 'status: ${oldState.status}';
}
if (newState.status != null) {
newStateInfo = 'status: ${newState.status}';
}
} catch (e) {
// This catch block handles cases where:
// 1. The 'status' property does not exist on the state object (NoSuchMethodError).
// 2. Accessing 'status' throws any other runtime error.
// In such scenarios, the `oldStateInfo` and `newStateInfo` variables
// will retain their initially truncated string representations,
// providing a fallback for states without a 'status' property.
// Log the error for debugging purposes, but do not rethrow to avoid
// crashing the observer.
log('Error accessing status property for ${bloc.runtimeType}: $e');
}

// Log the state change, including the BLoC type and the old and new state information.
log('onChange(${bloc.runtimeType}, $oldStateInfo -> $newStateInfo)');
}
Expand Down
1 change: 0 additions & 1 deletion lib/content_management/bloc/content_management_state.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ class ContentManagementState extends Equatable {
sources: sources ?? this.sources,
sourcesCursor: sourcesCursor ?? this.sourcesCursor,
sourcesHasMore: sourcesHasMore ?? this.sourcesHasMore,

exception: exception ?? this.exception,
);
}
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class CreateHeadlineBloc
CreateHeadlineBloc({
required DataRepository<Headline> headlinesRepository,
}) : _headlinesRepository = headlinesRepository,

super(const CreateHeadlineState()) {
on<CreateHeadlineTitleChanged>(_onTitleChanged);
on<CreateHeadlineExcerptChanged>(_onExcerptChanged);
Expand All @@ -24,8 +23,8 @@ class CreateHeadlineBloc
on<CreateHeadlineSourceChanged>(_onSourceChanged);
on<CreateHeadlineTopicChanged>(_onTopicChanged);
on<CreateHeadlineCountryChanged>(_onCountryChanged);
on<CreateHeadlineStatusChanged>(_onStatusChanged);
on<CreateHeadlineSubmitted>(_onSubmitted);
on<CreateHeadlineSavedAsDraft>(_onSavedAsDraft);
on<CreateHeadlinePublished>(_onPublished);
}

final DataRepository<Headline> _headlinesRepository;
Expand Down Expand Up @@ -81,24 +80,52 @@ class CreateHeadlineBloc
emit(state.copyWith(eventCountry: () => event.country));
}

void _onStatusChanged(
CreateHeadlineStatusChanged event,
/// Handles saving the headline as a draft.
Future<void> _onSavedAsDraft(
CreateHeadlineSavedAsDraft event,
Emitter<CreateHeadlineState> emit,
) {
emit(
state.copyWith(
contentStatus: event.status,
status: CreateHeadlineStatus.initial,
),
);
) async {
emit(state.copyWith(status: CreateHeadlineStatus.submitting));
try {
final now = DateTime.now();
final newHeadline = Headline(
id: _uuid.v4(),
title: state.title,
excerpt: state.excerpt,
url: state.url,
imageUrl: state.imageUrl,
source: state.source!,
eventCountry: state.eventCountry!,
topic: state.topic!,
createdAt: now,
updatedAt: now,
status: ContentStatus.draft,
);

await _headlinesRepository.create(item: newHeadline);
emit(
state.copyWith(
status: CreateHeadlineStatus.success,
createdHeadline: newHeadline,
),
);
} on HttpException catch (e) {
emit(state.copyWith(status: CreateHeadlineStatus.failure, exception: e));
} catch (e) {
emit(
state.copyWith(
status: CreateHeadlineStatus.failure,
exception: UnknownException('An unexpected error occurred: $e'),
),
);
}
}

Future<void> _onSubmitted(
CreateHeadlineSubmitted event,
/// Handles publishing the headline.
Future<void> _onPublished(
CreateHeadlinePublished event,
Emitter<CreateHeadlineState> emit,
) async {
if (!state.isFormValid) return;

emit(state.copyWith(status: CreateHeadlineStatus.submitting));
try {
final now = DateTime.now();
Expand All @@ -113,7 +140,7 @@ class CreateHeadlineBloc
topic: state.topic!,
createdAt: now,
updatedAt: now,
status: state.contentStatus,
status: ContentStatus.active,
);

await _headlinesRepository.create(item: newHeadline);
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,12 @@ final class CreateHeadlineCountryChanged extends CreateHeadlineEvent {
List<Object?> get props => [country];
}

/// Event for when the headline's status is changed.
final class CreateHeadlineStatusChanged extends CreateHeadlineEvent {
const CreateHeadlineStatusChanged(this.status);

final ContentStatus status;

@override
List<Object?> get props => [status];
/// Event to save the headline as a draft.
final class CreateHeadlineSavedAsDraft extends CreateHeadlineEvent {
const CreateHeadlineSavedAsDraft();
}

/// Event to signal that the form should be submitted.
final class CreateHeadlineSubmitted extends CreateHeadlineEvent {
const CreateHeadlineSubmitted();
/// Event to publish the headline.
final class CreateHeadlinePublished extends CreateHeadlineEvent {
const CreateHeadlinePublished();
}
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ final class CreateHeadlineState extends Equatable {
this.source,
this.topic,
this.eventCountry,
this.contentStatus = ContentStatus.active,
this.exception,
this.createdHeadline,
});
Expand All @@ -42,7 +41,6 @@ final class CreateHeadlineState extends Equatable {
final Source? source;
final Topic? topic;
final Country? eventCountry;
final ContentStatus contentStatus;
final HttpException? exception;
final Headline? createdHeadline;

Expand All @@ -65,7 +63,6 @@ final class CreateHeadlineState extends Equatable {
ValueGetter<Source?>? source,
ValueGetter<Topic?>? topic,
ValueGetter<Country?>? eventCountry,
ContentStatus? contentStatus,
HttpException? exception,
Headline? createdHeadline,
}) {
Expand All @@ -78,7 +75,6 @@ final class CreateHeadlineState extends Equatable {
source: source != null ? source() : this.source,
topic: topic != null ? topic() : this.topic,
eventCountry: eventCountry != null ? eventCountry() : this.eventCountry,
contentStatus: contentStatus ?? this.contentStatus,
exception: exception,
createdHeadline: createdHeadline ?? this.createdHeadline,
);
Expand All @@ -94,7 +90,6 @@ final class CreateHeadlineState extends Equatable {
source,
topic,
eventCountry,
contentStatus,
exception,
createdHeadline,
];
Expand Down
59 changes: 43 additions & 16 deletions lib/content_management/bloc/create_source/create_source_bloc.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
on<CreateSourceTypeChanged>(_onSourceTypeChanged);
on<CreateSourceLanguageChanged>(_onLanguageChanged);
on<CreateSourceHeadquartersChanged>(_onHeadquartersChanged);
on<CreateSourceStatusChanged>(_onStatusChanged);
on<CreateSourceSubmitted>(_onSubmitted);
on<CreateSourceSavedAsDraft>(_onSavedAsDraft);
on<CreateSourcePublished>(_onPublished);
}

final DataRepository<Source> _sourcesRepository;
Expand Down Expand Up @@ -70,24 +70,51 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
emit(state.copyWith(headquarters: () => event.headquarters));
}

void _onStatusChanged(
CreateSourceStatusChanged event,
/// Handles saving the source as a draft.
Future<void> _onSavedAsDraft(
CreateSourceSavedAsDraft event,
Emitter<CreateSourceState> emit,
) {
emit(
state.copyWith(
contentStatus: event.status,
status: CreateSourceStatus.initial,
),
);
) async {
emit(state.copyWith(status: CreateSourceStatus.submitting));
try {
final now = DateTime.now();
final newSource = Source(
id: _uuid.v4(),
name: state.name,
description: state.description,
url: state.url,
sourceType: state.sourceType!,
language: state.language!,
createdAt: now,
updatedAt: now,
headquarters: state.headquarters!,
status: ContentStatus.draft,
);

await _sourcesRepository.create(item: newSource);
emit(
state.copyWith(
status: CreateSourceStatus.success,
createdSource: newSource,
),
);
} on HttpException catch (e) {
emit(state.copyWith(status: CreateSourceStatus.failure, exception: e));
} catch (e) {
emit(
state.copyWith(
status: CreateSourceStatus.failure,
exception: UnknownException('An unexpected error occurred: $e'),
),
);
}
}

Future<void> _onSubmitted(
CreateSourceSubmitted event,
/// Handles publishing the source.
Future<void> _onPublished(
CreateSourcePublished event,
Emitter<CreateSourceState> emit,
) async {
if (!state.isFormValid) return;

emit(state.copyWith(status: CreateSourceStatus.submitting));
try {
final now = DateTime.now();
Expand All @@ -101,7 +128,7 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
createdAt: now,
updatedAt: now,
headquarters: state.headquarters!,
status: state.contentStatus,
status: ContentStatus.active,
);

await _sourcesRepository.create(item: newSource);
Expand Down
17 changes: 6 additions & 11 deletions lib/content_management/bloc/create_source/create_source_event.dart
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,12 @@ final class CreateSourceHeadquartersChanged extends CreateSourceEvent {
List<Object?> 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<Object?> get props => [status];
/// Event to save the source as a draft.
final class CreateSourceSavedAsDraft extends CreateSourceEvent {
const CreateSourceSavedAsDraft();
}

/// Event to signal that the form should be submitted.
final class CreateSourceSubmitted extends CreateSourceEvent {
const CreateSourceSubmitted();
/// Event to publish the source.
final class CreateSourcePublished extends CreateSourceEvent {
const CreateSourcePublished();
}
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ final class CreateSourceState extends Equatable {
this.sourceType,
this.language,
this.headquarters,
this.contentStatus = ContentStatus.active,
this.exception,
this.createdSource,
});
Expand All @@ -41,7 +40,6 @@ final class CreateSourceState extends Equatable {
final SourceType? sourceType;
final Language? language;
final Country? headquarters;
final ContentStatus contentStatus;
final HttpException? exception;
final Source? createdSource;

Expand All @@ -62,7 +60,6 @@ final class CreateSourceState extends Equatable {
ValueGetter<SourceType?>? sourceType,
ValueGetter<Language?>? language,
ValueGetter<Country?>? headquarters,
ContentStatus? contentStatus,
HttpException? exception,
Source? createdSource,
}) {
Expand All @@ -74,7 +71,6 @@ final class CreateSourceState extends Equatable {
sourceType: sourceType != null ? sourceType() : this.sourceType,
language: language != null ? language() : this.language,
headquarters: headquarters != null ? headquarters() : this.headquarters,
contentStatus: contentStatus ?? this.contentStatus,
exception: exception,
createdSource: createdSource ?? this.createdSource,
);
Expand All @@ -89,7 +85,6 @@ final class CreateSourceState extends Equatable {
sourceType,
language,
headquarters,
contentStatus,
exception,
createdSource,
];
Expand Down
Loading
Loading

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /