Skip to content

Commit 9ecddac

Browse files
committed
feat(content-management): add create source page
- Implements UI for new source creation - Integrates CreateSourceBloc for state - Adds form fields for source details - Handles loading, success, and error states - Connects to source and country repositories
1 parent 0f8b2cf commit 9ecddac

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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/create_source/create_source_bloc.dart';
6+
import 'package:ht_dashboard/content_management/bloc/edit_source/edit_source_bloc.dart';
7+
import 'package:ht_dashboard/l10n/l10n.dart';
8+
import 'package:ht_dashboard/shared/shared.dart';
9+
import 'package:ht_data_repository/ht_data_repository.dart';
10+
import 'package:ht_shared/ht_shared.dart';
11+
12+
/// {@template create_source_page}
13+
/// A page for creating a new source.
14+
/// It uses a [BlocProvider] to create and provide a [CreateSourceBloc].
15+
/// {@endtemplate}
16+
class CreateSourcePage extends StatelessWidget {
17+
/// {@macro create_source_page}
18+
const CreateSourcePage({super.key});
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return BlocProvider(
23+
create: (context) => CreateSourceBloc(
24+
sourcesRepository: context.read<HtDataRepository<Source>>(),
25+
countriesRepository: context.read<HtDataRepository<Country>>(),
26+
)..add(const CreateSourceDataLoaded()),
27+
child: const _CreateSourceView(),
28+
);
29+
}
30+
}
31+
32+
class _CreateSourceView extends StatefulWidget {
33+
const _CreateSourceView();
34+
35+
@override
36+
State<_CreateSourceView> createState() => _CreateSourceViewState();
37+
}
38+
39+
class _CreateSourceViewState extends State<_CreateSourceView> {
40+
final _formKey = GlobalKey<FormState>();
41+
42+
@override
43+
Widget build(BuildContext context) {
44+
final l10n = context.l10n;
45+
return Scaffold(
46+
appBar: AppBar(
47+
title: Text(l10n.createSource),
48+
actions: [
49+
BlocBuilder<CreateSourceBloc, CreateSourceState>(
50+
builder: (context, state) {
51+
if (state.status == CreateSourceStatus.submitting) {
52+
return const Padding(
53+
padding: EdgeInsets.only(right: AppSpacing.lg),
54+
child: SizedBox(
55+
width: 24,
56+
height: 24,
57+
child: CircularProgressIndicator(strokeWidth: 3),
58+
),
59+
);
60+
}
61+
return IconButton(
62+
icon: const Icon(Icons.save),
63+
tooltip: l10n.saveChanges,
64+
onPressed: state.isFormValid
65+
? () => context.read<CreateSourceBloc>().add(
66+
const CreateSourceSubmitted(),
67+
)
68+
: null,
69+
);
70+
},
71+
),
72+
],
73+
),
74+
body: BlocConsumer<CreateSourceBloc, CreateSourceState>(
75+
listenWhen: (previous, current) => previous.status != current.status,
76+
listener: (context, state) {
77+
if (state.status == CreateSourceStatus.success &&
78+
ModalRoute.of(context)!.isCurrent) {
79+
ScaffoldMessenger.of(context)
80+
..hideCurrentSnackBar()
81+
..showSnackBar(
82+
SnackBar(content: Text(l10n.sourceCreatedSuccessfully)),
83+
);
84+
context.read<ContentManagementBloc>().add(
85+
const LoadSourcesRequested(),
86+
);
87+
context.pop();
88+
}
89+
if (state.status == CreateSourceStatus.failure) {
90+
ScaffoldMessenger.of(context)
91+
..hideCurrentSnackBar()
92+
..showSnackBar(
93+
SnackBar(
94+
content: Text(state.errorMessage ?? l10n.unknownError),
95+
backgroundColor: Theme.of(context).colorScheme.error,
96+
),
97+
);
98+
}
99+
},
100+
builder: (context, state) {
101+
if (state.status == CreateSourceStatus.loading) {
102+
return LoadingStateWidget(
103+
icon: Icons.source,
104+
headline: l10n.loadingData,
105+
subheadline: l10n.pleaseWait,
106+
);
107+
}
108+
109+
if (state.status == CreateSourceStatus.failure &&
110+
state.countries.isEmpty) {
111+
return FailureStateWidget(
112+
message: state.errorMessage ?? l10n.unknownError,
113+
onRetry: () => context.read<CreateSourceBloc>().add(
114+
const CreateSourceDataLoaded(),
115+
),
116+
);
117+
}
118+
119+
return SingleChildScrollView(
120+
child: Padding(
121+
padding: const EdgeInsets.all(AppSpacing.lg),
122+
child: Form(
123+
key: _formKey,
124+
child: Column(
125+
crossAxisAlignment: CrossAxisAlignment.start,
126+
children: [
127+
TextFormField(
128+
initialValue: state.name,
129+
decoration: InputDecoration(
130+
labelText: l10n.sourceName,
131+
border: const OutlineInputBorder(),
132+
),
133+
onChanged: (value) => context
134+
.read<CreateSourceBloc>()
135+
.add(CreateSourceNameChanged(value)),
136+
),
137+
const SizedBox(height: AppSpacing.lg),
138+
TextFormField(
139+
initialValue: state.description,
140+
decoration: InputDecoration(
141+
labelText: l10n.description,
142+
border: const OutlineInputBorder(),
143+
),
144+
maxLines: 3,
145+
onChanged: (value) => context
146+
.read<CreateSourceBloc>()
147+
.add(CreateSourceDescriptionChanged(value)),
148+
),
149+
const SizedBox(height: AppSpacing.lg),
150+
TextFormField(
151+
initialValue: state.url,
152+
decoration: InputDecoration(
153+
labelText: l10n.sourceUrl,
154+
border: const OutlineInputBorder(),
155+
),
156+
onChanged: (value) => context
157+
.read<CreateSourceBloc>()
158+
.add(CreateSourceUrlChanged(value)),
159+
),
160+
const SizedBox(height: AppSpacing.lg),
161+
TextFormField(
162+
initialValue: state.language,
163+
decoration: InputDecoration(
164+
labelText: l10n.language,
165+
border: const OutlineInputBorder(),
166+
),
167+
onChanged: (value) => context
168+
.read<CreateSourceBloc>()
169+
.add(CreateSourceLanguageChanged(value)),
170+
),
171+
const SizedBox(height: AppSpacing.lg),
172+
DropdownButtonFormField<SourceType?>(
173+
value: state.sourceType,
174+
decoration: InputDecoration(
175+
labelText: l10n.sourceType,
176+
border: const OutlineInputBorder(),
177+
),
178+
items: [
179+
DropdownMenuItem(value: null, child: Text(l10n.none)),
180+
...SourceType.values.map(
181+
(type) => DropdownMenuItem(
182+
value: type,
183+
child: Text(type.localizedName(l10n)),
184+
),
185+
),
186+
],
187+
onChanged: (value) => context
188+
.read<CreateSourceBloc>()
189+
.add(CreateSourceTypeChanged(value)),
190+
),
191+
const SizedBox(height: AppSpacing.lg),
192+
DropdownButtonFormField<Country?>(
193+
value: state.headquarters,
194+
decoration: InputDecoration(
195+
labelText: l10n.headquarters,
196+
border: const OutlineInputBorder(),
197+
),
198+
items: [
199+
DropdownMenuItem(value: null, child: Text(l10n.none)),
200+
...state.countries.map(
201+
(country) => DropdownMenuItem(
202+
value: country,
203+
child: Text(country.name),
204+
),
205+
),
206+
],
207+
onChanged: (value) => context
208+
.read<CreateSourceBloc>()
209+
.add(CreateSourceHeadquartersChanged(value)),
210+
),
211+
],
212+
),
213+
),
214+
),
215+
);
216+
},
217+
),
218+
);
219+
}
220+
}

0 commit comments

Comments
 (0)