Skip to content

Commit d519424

Browse files
committed
feat(content_mgmt): add edit source page
- Implemented edit source form - Added bloc for state management - Integrated with data repository
1 parent 5fac073 commit d519424

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:go_router/go_router.dart';
4+
import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart';
5+
import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart';
6+
import 'package:ht_dashboard/l10n/l10n.dart';
7+
import 'package:ht_dashboard/shared/shared.dart';
8+
import 'package:ht_data_repository/ht_data_repository.dart';
9+
import 'package:ht_shared/ht_shared.dart';
10+
11+
/// {@template edit_source_page}
12+
/// A page for editing an existing source.
13+
/// It uses a [BlocProvider] to create and provide an [EditSourceBloc].
14+
/// {@endtemplate}
15+
class EditSourcePage extends StatelessWidget {
16+
/// {@macro edit_source_page}
17+
const EditSourcePage({required this.sourceId, super.key});
18+
19+
/// The ID of the source to be edited.
20+
final String sourceId;
21+
22+
@override
23+
Widget build(BuildContext context) {
24+
return BlocProvider(
25+
create: (context) => EditSourceBloc(
26+
sourcesRepository: context.read<HtDataRepository<Source>>(),
27+
countriesRepository: context.read<HtDataRepository<Country>>(),
28+
sourceId: sourceId,
29+
)..add(const EditSourceLoaded()),
30+
child: const _EditSourceView(),
31+
);
32+
}
33+
}
34+
35+
class _EditSourceView extends StatefulWidget {
36+
const _EditSourceView();
37+
38+
@override
39+
State<_EditSourceView> createState() => _EditSourceViewState();
40+
}
41+
42+
class _EditSourceViewState extends State<_EditSourceView> {
43+
final _formKey = GlobalKey<FormState>();
44+
late final TextEditingController _nameController;
45+
late final TextEditingController _descriptionController;
46+
late final TextEditingController _urlController;
47+
late final TextEditingController _languageController;
48+
49+
@override
50+
void initState() {
51+
super.initState();
52+
final state = context.read<EditSourceBloc>().state;
53+
_nameController = TextEditingController(text: state.name);
54+
_descriptionController = TextEditingController(text: state.description);
55+
_urlController = TextEditingController(text: state.url);
56+
_languageController = TextEditingController(text: state.language);
57+
}
58+
59+
@override
60+
void dispose() {
61+
_nameController.dispose();
62+
_descriptionController.dispose();
63+
_urlController.dispose();
64+
_languageController.dispose();
65+
super.dispose();
66+
}
67+
68+
@override
69+
Widget build(BuildContext context) {
70+
final l10n = context.l10n;
71+
return Scaffold(
72+
appBar: AppBar(
73+
title: Text(l10n.editSource),
74+
actions: [
75+
BlocBuilder<EditSourceBloc, EditSourceState>(
76+
builder: (context, state) {
77+
if (state.status == EditSourceStatus.submitting) {
78+
return const Padding(
79+
padding: EdgeInsets.only(right: AppSpacing.lg),
80+
child: SizedBox(
81+
width: 24,
82+
height: 24,
83+
child: CircularProgressIndicator(strokeWidth: 3),
84+
),
85+
);
86+
}
87+
return IconButton(
88+
icon: const Icon(Icons.save),
89+
tooltip: l10n.saveChanges,
90+
onPressed: state.isFormValid
91+
? () => context.read<EditSourceBloc>().add(
92+
const EditSourceSubmitted(),
93+
)
94+
: null,
95+
);
96+
},
97+
),
98+
],
99+
),
100+
body: BlocConsumer<EditSourceBloc, EditSourceState>(
101+
listenWhen: (previous, current) =>
102+
previous.status != current.status ||
103+
previous.initialSource != current.initialSource,
104+
listener: (context, state) {
105+
if (state.status == EditSourceStatus.success &&
106+
state.initialSource != null &&
107+
ModalRoute.of(context)!.isCurrent) {
108+
ScaffoldMessenger.of(context)
109+
..hideCurrentSnackBar()
110+
..showSnackBar(
111+
SnackBar(content: Text(l10n.sourceUpdatedSuccessfully)),
112+
);
113+
context.read<ContentManagementBloc>().add(
114+
const LoadSourcesRequested(),
115+
);
116+
context.pop();
117+
}
118+
if (state.status == EditSourceStatus.failure) {
119+
ScaffoldMessenger.of(context)
120+
..hideCurrentSnackBar()
121+
..showSnackBar(
122+
SnackBar(
123+
content: Text(state.errorMessage ?? l10n.unknownError),
124+
backgroundColor: Theme.of(context).colorScheme.error,
125+
),
126+
);
127+
}
128+
if (state.initialSource != null) {
129+
_nameController.text = state.name;
130+
_descriptionController.text = state.description;
131+
_urlController.text = state.url;
132+
_languageController.text = state.language;
133+
}
134+
},
135+
builder: (context, state) {
136+
if (state.status == EditSourceStatus.loading) {
137+
return LoadingStateWidget(
138+
icon: Icons.source,
139+
headline: l10n.loadingSource,
140+
subheadline: l10n.pleaseWait,
141+
);
142+
}
143+
144+
if (state.status == EditSourceStatus.failure &&
145+
state.initialSource == null) {
146+
return FailureStateWidget(
147+
message: state.errorMessage ?? l10n.unknownError,
148+
onRetry: () =>
149+
context.read<EditSourceBloc>().add(const EditSourceLoaded()),
150+
);
151+
}
152+
153+
// Find the correct Country instance from the list to ensure
154+
// the Dropdown can display the selection correctly.
155+
Country? selectedHeadquarters;
156+
if (state.headquarters != null) {
157+
try {
158+
selectedHeadquarters = state.countries.firstWhere(
159+
(c) => c.id == state.headquarters!.id,
160+
);
161+
} catch (_) {
162+
selectedHeadquarters = null;
163+
}
164+
}
165+
166+
return SingleChildScrollView(
167+
child: Padding(
168+
padding: const EdgeInsets.all(AppSpacing.lg),
169+
child: Form(
170+
key: _formKey,
171+
child: Column(
172+
crossAxisAlignment: CrossAxisAlignment.start,
173+
children: [
174+
TextFormField(
175+
controller: _nameController,
176+
decoration: InputDecoration(
177+
labelText: l10n.sourceName,
178+
border: const OutlineInputBorder(),
179+
),
180+
onChanged: (value) => context.read<EditSourceBloc>().add(
181+
EditSourceNameChanged(value),
182+
),
183+
),
184+
const SizedBox(height: AppSpacing.lg),
185+
TextFormField(
186+
controller: _descriptionController,
187+
decoration: InputDecoration(
188+
labelText: l10n.description,
189+
border: const OutlineInputBorder(),
190+
),
191+
maxLines: 3,
192+
onChanged: (value) => context.read<EditSourceBloc>().add(
193+
EditSourceDescriptionChanged(value),
194+
),
195+
),
196+
const SizedBox(height: AppSpacing.lg),
197+
TextFormField(
198+
controller: _urlController,
199+
decoration: InputDecoration(
200+
labelText: l10n.sourceUrl,
201+
border: const OutlineInputBorder(),
202+
),
203+
onChanged: (value) => context.read<EditSourceBloc>().add(
204+
EditSourceUrlChanged(value),
205+
),
206+
),
207+
const SizedBox(height: AppSpacing.lg),
208+
TextFormField(
209+
controller: _languageController,
210+
decoration: InputDecoration(
211+
labelText: l10n.language,
212+
border: const OutlineInputBorder(),
213+
),
214+
onChanged: (value) => context.read<EditSourceBloc>().add(
215+
EditSourceLanguageChanged(value),
216+
),
217+
),
218+
const SizedBox(height: AppSpacing.lg),
219+
DropdownButtonFormField<SourceType?>(
220+
value: state.sourceType,
221+
decoration: InputDecoration(
222+
labelText: l10n.sourceType,
223+
border: const OutlineInputBorder(),
224+
),
225+
items: [
226+
DropdownMenuItem(value: null, child: Text(l10n.none)),
227+
...SourceType.values.map(
228+
(type) => DropdownMenuItem(
229+
value: type,
230+
child: Text(type.localizedName(l10n)),
231+
),
232+
),
233+
],
234+
onChanged: (value) => context.read<EditSourceBloc>().add(
235+
EditSourceTypeChanged(value),
236+
),
237+
),
238+
const SizedBox(height: AppSpacing.lg),
239+
DropdownButtonFormField<Country?>(
240+
value: selectedHeadquarters,
241+
decoration: InputDecoration(
242+
labelText: l10n.headquarters,
243+
border: const OutlineInputBorder(),
244+
),
245+
items: [
246+
DropdownMenuItem(value: null, child: Text(l10n.none)),
247+
...state.countries.map(
248+
(country) => DropdownMenuItem(
249+
value: country,
250+
child: Text(country.name),
251+
),
252+
),
253+
],
254+
onChanged: (value) => context.read<EditSourceBloc>().add(
255+
EditSourceHeadquartersChanged(value),
256+
),
257+
),
258+
],
259+
),
260+
),
261+
),
262+
);
263+
},
264+
),
265+
);
266+
}
267+
}

0 commit comments

Comments
 (0)