Skip to content

Commit 5fac073

Browse files
committed
feat: implement edit source bloc
- Manages state of single source editing - Fetches source and countries data - Handles form input changes - Submits updated source data
1 parent 99f07fc commit 5fac073

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:equatable/equatable.dart';
3+
import 'package:flutter/foundation.dart';
4+
import 'package:ht_dashboard/l10n/app_localizations.dart';
5+
import 'package:ht_data_repository/ht_data_repository.dart';
6+
import 'package:ht_http_client/ht_http_client.dart';
7+
import 'package:ht_shared/ht_shared.dart';
8+
import 'package:ht_dashboard/l10n/l10n.dart';
9+
10+
part 'edit_source_event.dart';
11+
part 'edit_source_state.dart';
12+
13+
/// A BLoC to manage the state of editing a single source.
14+
class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
15+
/// {@macro edit_source_bloc}
16+
EditSourceBloc({
17+
required HtDataRepository<Source> sourcesRepository,
18+
required HtDataRepository<Country> countriesRepository,
19+
required String sourceId,
20+
}) : _sourcesRepository = sourcesRepository,
21+
_countriesRepository = countriesRepository,
22+
_sourceId = sourceId,
23+
super(const EditSourceState()) {
24+
on<EditSourceLoaded>(_onLoaded);
25+
on<EditSourceNameChanged>(_onNameChanged);
26+
on<EditSourceDescriptionChanged>(_onDescriptionChanged);
27+
on<EditSourceUrlChanged>(_onUrlChanged);
28+
on<EditSourceTypeChanged>(_onSourceTypeChanged);
29+
on<EditSourceLanguageChanged>(_onLanguageChanged);
30+
on<EditSourceHeadquartersChanged>(_onHeadquartersChanged);
31+
on<EditSourceSubmitted>(_onSubmitted);
32+
}
33+
34+
final HtDataRepository<Source> _sourcesRepository;
35+
final HtDataRepository<Country> _countriesRepository;
36+
final String _sourceId;
37+
38+
Future<void> _onLoaded(
39+
EditSourceLoaded event,
40+
Emitter<EditSourceState> emit,
41+
) async {
42+
emit(state.copyWith(status: EditSourceStatus.loading));
43+
try {
44+
final [sourceResponse, countriesResponse] = await Future.wait([
45+
_sourcesRepository.read(id: _sourceId),
46+
_countriesRepository.readAll(),
47+
]);
48+
final source = sourceResponse as Source;
49+
final countries = (countriesResponse as PaginatedResponse<Country>).items;
50+
emit(
51+
state.copyWith(
52+
status: EditSourceStatus.initial,
53+
initialSource: source,
54+
name: source.name,
55+
description: source.description ?? '',
56+
url: source.url ?? '',
57+
sourceType: () => source.sourceType,
58+
language: source.language ?? '',
59+
headquarters: () => source.headquarters,
60+
countries: countries,
61+
),
62+
);
63+
} on HtHttpException catch (e) {
64+
emit(
65+
state.copyWith(
66+
status: EditSourceStatus.failure,
67+
errorMessage: e.message,
68+
),
69+
);
70+
} catch (e) {
71+
emit(
72+
state.copyWith(
73+
status: EditSourceStatus.failure,
74+
errorMessage: e.toString(),
75+
),
76+
);
77+
}
78+
}
79+
80+
void _onNameChanged(
81+
EditSourceNameChanged event,
82+
Emitter<EditSourceState> emit,
83+
) {
84+
emit(state.copyWith(name: event.name, status: EditSourceStatus.initial));
85+
}
86+
87+
void _onDescriptionChanged(
88+
EditSourceDescriptionChanged event,
89+
Emitter<EditSourceState> emit,
90+
) {
91+
emit(
92+
state.copyWith(
93+
description: event.description,
94+
status: EditSourceStatus.initial,
95+
),
96+
);
97+
}
98+
99+
void _onUrlChanged(
100+
EditSourceUrlChanged event,
101+
Emitter<EditSourceState> emit,
102+
) {
103+
emit(state.copyWith(url: event.url, status: EditSourceStatus.initial));
104+
}
105+
106+
void _onSourceTypeChanged(
107+
EditSourceTypeChanged event,
108+
Emitter<EditSourceState> emit,
109+
) {
110+
emit(
111+
state.copyWith(
112+
sourceType: () => event.sourceType,
113+
status: EditSourceStatus.initial,
114+
),
115+
);
116+
}
117+
118+
void _onLanguageChanged(
119+
EditSourceLanguageChanged event,
120+
Emitter<EditSourceState> emit,
121+
) {
122+
emit(
123+
state.copyWith(
124+
language: event.language,
125+
status: EditSourceStatus.initial,
126+
),
127+
);
128+
}
129+
130+
void _onHeadquartersChanged(
131+
EditSourceHeadquartersChanged event,
132+
Emitter<EditSourceState> emit,
133+
) {
134+
emit(
135+
state.copyWith(
136+
headquarters: () => event.headquarters,
137+
status: EditSourceStatus.initial,
138+
),
139+
);
140+
}
141+
142+
Future<void> _onSubmitted(
143+
EditSourceSubmitted event,
144+
Emitter<EditSourceState> emit,
145+
) async {
146+
if (!state.isFormValid) return;
147+
148+
final initialSource = state.initialSource;
149+
if (initialSource == null) {
150+
emit(
151+
state.copyWith(
152+
status: EditSourceStatus.failure,
153+
errorMessage: 'Cannot update: Original source data not loaded.',
154+
),
155+
);
156+
return;
157+
}
158+
159+
emit(state.copyWith(status: EditSourceStatus.submitting));
160+
try {
161+
final updatedSource = initialSource.copyWith(
162+
name: state.name,
163+
description: state.description.isNotEmpty ? state.description : null,
164+
url: state.url.isNotEmpty ? state.url : null,
165+
sourceType: state.sourceType,
166+
language: state.language.isNotEmpty ? state.language : null,
167+
headquarters: state.headquarters,
168+
);
169+
170+
await _sourcesRepository.update(id: _sourceId, item: updatedSource);
171+
emit(state.copyWith(status: EditSourceStatus.success));
172+
} on HtHttpException catch (e) {
173+
emit(
174+
state.copyWith(
175+
status: EditSourceStatus.failure,
176+
errorMessage: e.message,
177+
),
178+
);
179+
} catch (e) {
180+
emit(
181+
state.copyWith(
182+
status: EditSourceStatus.failure,
183+
errorMessage: e.toString(),
184+
),
185+
);
186+
}
187+
}
188+
}
189+
190+
/// Adds localization support to the [SourceType] enum.
191+
extension SourceTypeL10n on SourceType {
192+
/// Returns the localized name for the source type.
193+
///
194+
/// This requires an [AppLocalizations] instance, which is typically
195+
/// retrieved from the build context.
196+
String localizedName(AppLocalizations l10n) {
197+
switch (this) {
198+
case SourceType.newsAgency:
199+
return l10n.sourceTypeNewsAgency;
200+
case SourceType.localNewsOutlet:
201+
return l10n.sourceTypeLocalNewsOutlet;
202+
case SourceType.nationalNewsOutlet:
203+
return l10n.sourceTypeNationalNewsOutlet;
204+
case SourceType.internationalNewsOutlet:
205+
return l10n.sourceTypeInternationalNewsOutlet;
206+
case SourceType.specializedPublisher:
207+
return l10n.sourceTypeSpecializedPublisher;
208+
case SourceType.blog:
209+
return l10n.sourceTypeBlog;
210+
case SourceType.governmentSource:
211+
return l10n.sourceTypeGovernmentSource;
212+
case SourceType.aggregator:
213+
return l10n.sourceTypeAggregator;
214+
case SourceType.other:
215+
return l10n.sourceTypeOther;
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)