Skip to content

Commit fcdb273

Browse files
committed
feat: add edit category page
- Implemented edit category UI - Added bloc logic - Integrated with repository
1 parent 3436a24 commit fcdb273

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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_category/edit_category_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_category_page}
12+
/// A page for editing an existing category.
13+
/// It uses a [BlocProvider] to create and provide an [EditCategoryBloc].
14+
/// {@endtemplate}
15+
class EditCategoryPage extends StatelessWidget {
16+
/// {@macro edit_category_page}
17+
const EditCategoryPage({required this.categoryId, super.key});
18+
19+
/// The ID of the category to be edited.
20+
final String categoryId;
21+
22+
@override
23+
Widget build(BuildContext context) {
24+
return BlocProvider(
25+
create: (context) => EditCategoryBloc(
26+
categoriesRepository: context.read<HtDataRepository<Category>>(),
27+
categoryId: categoryId,
28+
)..add(const EditCategoryLoaded()),
29+
child: const _EditCategoryView(),
30+
);
31+
}
32+
}
33+
34+
class _EditCategoryView extends StatefulWidget {
35+
const _EditCategoryView();
36+
37+
@override
38+
State<_EditCategoryView> createState() => _EditCategoryViewState();
39+
}
40+
41+
class _EditCategoryViewState extends State<_EditCategoryView> {
42+
final _formKey = GlobalKey<FormState>();
43+
late final TextEditingController _nameController;
44+
late final TextEditingController _descriptionController;
45+
late final TextEditingController _iconUrlController;
46+
47+
@override
48+
void initState() {
49+
super.initState();
50+
final state = context.read<EditCategoryBloc>().state;
51+
_nameController = TextEditingController(text: state.name);
52+
_descriptionController = TextEditingController(text: state.description);
53+
_iconUrlController = TextEditingController(text: state.iconUrl);
54+
}
55+
56+
@override
57+
void dispose() {
58+
_nameController.dispose();
59+
_descriptionController.dispose();
60+
_iconUrlController.dispose();
61+
super.dispose();
62+
}
63+
64+
@override
65+
Widget build(BuildContext context) {
66+
final l10n = context.l10n;
67+
return Scaffold(
68+
appBar: AppBar(
69+
title: Text(l10n.editCategory),
70+
actions: [
71+
BlocBuilder<EditCategoryBloc, EditCategoryState>(
72+
builder: (context, state) {
73+
if (state.status == EditCategoryStatus.submitting) {
74+
return const Padding(
75+
padding: EdgeInsets.only(right: AppSpacing.lg),
76+
child: SizedBox(
77+
width: 24,
78+
height: 24,
79+
child: CircularProgressIndicator(strokeWidth: 3),
80+
),
81+
);
82+
}
83+
return IconButton(
84+
icon: const Icon(Icons.save),
85+
tooltip: l10n.saveChanges,
86+
onPressed: state.isFormValid
87+
? () => context.read<EditCategoryBloc>().add(
88+
const EditCategorySubmitted(),
89+
)
90+
: null,
91+
);
92+
},
93+
),
94+
],
95+
),
96+
body: BlocConsumer<EditCategoryBloc, EditCategoryState>(
97+
listenWhen: (previous, current) =>
98+
previous.status != current.status ||
99+
previous.initialCategory != current.initialCategory,
100+
listener: (context, state) {
101+
if (state.status == EditCategoryStatus.success &&
102+
state.initialCategory != null &&
103+
ModalRoute.of(context)!.isCurrent) {
104+
ScaffoldMessenger.of(context)
105+
..hideCurrentSnackBar()
106+
..showSnackBar(
107+
// TODO(l10n): Localize this message.
108+
const SnackBar(content: Text('Category updated successfully.')),
109+
);
110+
context.read<ContentManagementBloc>().add(
111+
const LoadCategoriesRequested(),
112+
);
113+
context.pop();
114+
}
115+
if (state.status == EditCategoryStatus.failure) {
116+
ScaffoldMessenger.of(context)
117+
..hideCurrentSnackBar()
118+
..showSnackBar(
119+
SnackBar(
120+
content: Text(state.errorMessage ?? l10n.unknownError),
121+
backgroundColor: Theme.of(context).colorScheme.error,
122+
),
123+
);
124+
}
125+
if (state.initialCategory != null) {
126+
_nameController.text = state.name;
127+
_descriptionController.text = state.description;
128+
_iconUrlController.text = state.iconUrl;
129+
}
130+
},
131+
builder: (context, state) {
132+
if (state.status == EditCategoryStatus.loading) {
133+
return LoadingStateWidget(
134+
icon: Icons.category,
135+
// TODO(l10n): Localize this message.
136+
headline: 'Loading Category...',
137+
subheadline: l10n.pleaseWait,
138+
);
139+
}
140+
141+
if (state.status == EditCategoryStatus.failure &&
142+
state.initialCategory == null) {
143+
return FailureStateWidget(
144+
message: state.errorMessage ?? l10n.unknownError,
145+
onRetry: () => context.read<EditCategoryBloc>().add(
146+
const EditCategoryLoaded(),
147+
),
148+
);
149+
}
150+
151+
return SingleChildScrollView(
152+
child: Padding(
153+
padding: const EdgeInsets.all(AppSpacing.lg),
154+
child: Form(
155+
key: _formKey,
156+
child: Column(
157+
crossAxisAlignment: CrossAxisAlignment.start,
158+
children: [
159+
TextFormField(
160+
controller: _nameController,
161+
decoration: InputDecoration(
162+
labelText: l10n.categoryName,
163+
border: const OutlineInputBorder(),
164+
),
165+
onChanged: (value) => context
166+
.read<EditCategoryBloc>()
167+
.add(EditCategoryNameChanged(value)),
168+
),
169+
const SizedBox(height: AppSpacing.lg),
170+
TextFormField(
171+
controller: _descriptionController,
172+
decoration: InputDecoration(
173+
labelText: l10n.description,
174+
border: const OutlineInputBorder(),
175+
),
176+
maxLines: 3,
177+
onChanged: (value) => context
178+
.read<EditCategoryBloc>()
179+
.add(EditCategoryDescriptionChanged(value)),
180+
),
181+
const SizedBox(height: AppSpacing.lg),
182+
TextFormField(
183+
controller: _iconUrlController,
184+
decoration: InputDecoration(
185+
labelText: l10n.iconUrl,
186+
border: const OutlineInputBorder(),
187+
),
188+
onChanged: (value) => context
189+
.read<EditCategoryBloc>()
190+
.add(EditCategoryIconUrlChanged(value)),
191+
),
192+
],
193+
),
194+
),
195+
),
196+
);
197+
},
198+
),
199+
);
200+
}
201+
}

0 commit comments

Comments
 (0)