Skip to content

Commit 6db818d

Browse files
authored
Merge pull request #16 from headlines-toolkit/feature_create_source_within_content_management
Feature_create_source_within_content_management
2 parents 27e4d58 + bd68c4d commit 6db818d

File tree

10 files changed

+555
-4
lines changed

10 files changed

+555
-4
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:equatable/equatable.dart';
3+
import 'package:flutter/foundation.dart';
4+
import 'package:ht_data_repository/ht_data_repository.dart';
5+
import 'package:ht_shared/ht_shared.dart';
6+
7+
part 'create_source_event.dart';
8+
part 'create_source_state.dart';
9+
10+
/// A BLoC to manage the state of creating a new source.
11+
class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
12+
/// {@macro create_source_bloc}
13+
CreateSourceBloc({
14+
required HtDataRepository<Source> sourcesRepository,
15+
required HtDataRepository<Country> countriesRepository,
16+
}) : _sourcesRepository = sourcesRepository,
17+
_countriesRepository = countriesRepository,
18+
super(const CreateSourceState()) {
19+
on<CreateSourceDataLoaded>(_onDataLoaded);
20+
on<CreateSourceNameChanged>(_onNameChanged);
21+
on<CreateSourceDescriptionChanged>(_onDescriptionChanged);
22+
on<CreateSourceUrlChanged>(_onUrlChanged);
23+
on<CreateSourceTypeChanged>(_onSourceTypeChanged);
24+
on<CreateSourceLanguageChanged>(_onLanguageChanged);
25+
on<CreateSourceHeadquartersChanged>(_onHeadquartersChanged);
26+
on<CreateSourceSubmitted>(_onSubmitted);
27+
}
28+
29+
final HtDataRepository<Source> _sourcesRepository;
30+
final HtDataRepository<Country> _countriesRepository;
31+
32+
Future<void> _onDataLoaded(
33+
CreateSourceDataLoaded event,
34+
Emitter<CreateSourceState> emit,
35+
) async {
36+
emit(state.copyWith(status: CreateSourceStatus.loading));
37+
try {
38+
final countriesResponse = await _countriesRepository.readAll();
39+
final countries = (countriesResponse as PaginatedResponse<Country>).items;
40+
41+
emit(
42+
state.copyWith(
43+
status: CreateSourceStatus.initial,
44+
countries: countries,
45+
),
46+
);
47+
} on HtHttpException catch (e) {
48+
emit(
49+
state.copyWith(
50+
status: CreateSourceStatus.failure,
51+
errorMessage: e.message,
52+
),
53+
);
54+
} catch (e) {
55+
emit(
56+
state.copyWith(
57+
status: CreateSourceStatus.failure,
58+
errorMessage: e.toString(),
59+
),
60+
);
61+
}
62+
}
63+
64+
void _onNameChanged(
65+
CreateSourceNameChanged event,
66+
Emitter<CreateSourceState> emit,
67+
) {
68+
emit(state.copyWith(name: event.name));
69+
}
70+
71+
void _onDescriptionChanged(
72+
CreateSourceDescriptionChanged event,
73+
Emitter<CreateSourceState> emit,
74+
) {
75+
emit(state.copyWith(description: event.description));
76+
}
77+
78+
void _onUrlChanged(
79+
CreateSourceUrlChanged event,
80+
Emitter<CreateSourceState> emit,
81+
) {
82+
emit(state.copyWith(url: event.url));
83+
}
84+
85+
void _onSourceTypeChanged(
86+
CreateSourceTypeChanged event,
87+
Emitter<CreateSourceState> emit,
88+
) {
89+
emit(state.copyWith(sourceType: () => event.sourceType));
90+
}
91+
92+
void _onLanguageChanged(
93+
CreateSourceLanguageChanged event,
94+
Emitter<CreateSourceState> emit,
95+
) {
96+
emit(state.copyWith(language: event.language));
97+
}
98+
99+
void _onHeadquartersChanged(
100+
CreateSourceHeadquartersChanged event,
101+
Emitter<CreateSourceState> emit,
102+
) {
103+
emit(state.copyWith(headquarters: () => event.headquarters));
104+
}
105+
106+
Future<void> _onSubmitted(
107+
CreateSourceSubmitted event,
108+
Emitter<CreateSourceState> emit,
109+
) async {
110+
if (!state.isFormValid) return;
111+
112+
emit(state.copyWith(status: CreateSourceStatus.submitting));
113+
try {
114+
final newSource = Source(
115+
name: state.name,
116+
description: state.description.isNotEmpty ? state.description : null,
117+
url: state.url.isNotEmpty ? state.url : null,
118+
sourceType: state.sourceType,
119+
language: state.language.isNotEmpty ? state.language : null,
120+
headquarters: state.headquarters,
121+
);
122+
123+
await _sourcesRepository.create(item: newSource);
124+
emit(state.copyWith(status: CreateSourceStatus.success));
125+
} on HtHttpException catch (e) {
126+
emit(
127+
state.copyWith(
128+
status: CreateSourceStatus.failure,
129+
errorMessage: e.message,
130+
),
131+
);
132+
} catch (e) {
133+
emit(
134+
state.copyWith(
135+
status: CreateSourceStatus.failure,
136+
errorMessage: e.toString(),
137+
),
138+
);
139+
}
140+
}
141+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
part of 'create_source_bloc.dart';
2+
3+
/// Base class for all events related to the [CreateSourceBloc].
4+
sealed class CreateSourceEvent extends Equatable {
5+
const CreateSourceEvent();
6+
7+
@override
8+
List<Object?> get props => [];
9+
}
10+
11+
/// Event to signal that the data for dropdowns should be loaded.
12+
final class CreateSourceDataLoaded extends CreateSourceEvent {
13+
const CreateSourceDataLoaded();
14+
}
15+
16+
/// Event for when the source's name is changed.
17+
final class CreateSourceNameChanged extends CreateSourceEvent {
18+
const CreateSourceNameChanged(this.name);
19+
final String name;
20+
@override
21+
List<Object> get props => [name];
22+
}
23+
24+
/// Event for when the source's description is changed.
25+
final class CreateSourceDescriptionChanged extends CreateSourceEvent {
26+
const CreateSourceDescriptionChanged(this.description);
27+
final String description;
28+
@override
29+
List<Object> get props => [description];
30+
}
31+
32+
/// Event for when the source's URL is changed.
33+
final class CreateSourceUrlChanged extends CreateSourceEvent {
34+
const CreateSourceUrlChanged(this.url);
35+
final String url;
36+
@override
37+
List<Object> get props => [url];
38+
}
39+
40+
/// Event for when the source's type is changed.
41+
final class CreateSourceTypeChanged extends CreateSourceEvent {
42+
const CreateSourceTypeChanged(this.sourceType);
43+
final SourceType? sourceType;
44+
@override
45+
List<Object?> get props => [sourceType];
46+
}
47+
48+
/// Event for when the source's language is changed.
49+
final class CreateSourceLanguageChanged extends CreateSourceEvent {
50+
const CreateSourceLanguageChanged(this.language);
51+
final String language;
52+
@override
53+
List<Object> get props => [language];
54+
}
55+
56+
/// Event for when the source's headquarters is changed.
57+
final class CreateSourceHeadquartersChanged extends CreateSourceEvent {
58+
const CreateSourceHeadquartersChanged(this.headquarters);
59+
final Country? headquarters;
60+
@override
61+
List<Object?> get props => [headquarters];
62+
}
63+
64+
/// Event to signal that the form should be submitted.
65+
final class CreateSourceSubmitted extends CreateSourceEvent {
66+
const CreateSourceSubmitted();
67+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
part of 'create_source_bloc.dart';
2+
3+
/// Represents the status of the create source operation.
4+
enum CreateSourceStatus {
5+
/// Initial state, before any data is loaded.
6+
initial,
7+
8+
/// Data is being loaded.
9+
loading,
10+
11+
/// An operation completed successfully.
12+
success,
13+
14+
/// An error occurred.
15+
failure,
16+
17+
/// The form is being submitted.
18+
submitting,
19+
}
20+
21+
/// The state for the [CreateSourceBloc].
22+
final class CreateSourceState extends Equatable {
23+
/// {@macro create_source_state}
24+
const CreateSourceState({
25+
this.status = CreateSourceStatus.initial,
26+
this.name = '',
27+
this.description = '',
28+
this.url = '',
29+
this.sourceType,
30+
this.language = '',
31+
this.headquarters,
32+
this.countries = const [],
33+
this.errorMessage,
34+
});
35+
36+
final CreateSourceStatus status;
37+
final String name;
38+
final String description;
39+
final String url;
40+
final SourceType? sourceType;
41+
final String language;
42+
final Country? headquarters;
43+
final List<Country> countries;
44+
final String? errorMessage;
45+
46+
/// Returns true if the form is valid and can be submitted.
47+
bool get isFormValid => name.isNotEmpty;
48+
49+
CreateSourceState copyWith({
50+
CreateSourceStatus? status,
51+
String? name,
52+
String? description,
53+
String? url,
54+
ValueGetter<SourceType?>? sourceType,
55+
String? language,
56+
ValueGetter<Country?>? headquarters,
57+
List<Country>? countries,
58+
String? errorMessage,
59+
}) {
60+
return CreateSourceState(
61+
status: status ?? this.status,
62+
name: name ?? this.name,
63+
description: description ?? this.description,
64+
url: url ?? this.url,
65+
sourceType: sourceType != null ? sourceType() : this.sourceType,
66+
language: language ?? this.language,
67+
headquarters: headquarters != null ? headquarters() : this.headquarters,
68+
countries: countries ?? this.countries,
69+
errorMessage: errorMessage,
70+
);
71+
}
72+
73+
@override
74+
List<Object?> get props => [
75+
status,
76+
name,
77+
description,
78+
url,
79+
sourceType,
80+
language,
81+
headquarters,
82+
countries,
83+
errorMessage,
84+
];
85+
}

0 commit comments

Comments
 (0)