Skip to content

Commit 7a238be

Browse files
committed
feat(content_management): add ArchivedHeadlinesPage with data table and state management
1 parent 7d5272d commit 7a238be

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import 'package:core/core.dart';
2+
import 'package:data_repository/data_repository.dart';
3+
import 'package:data_table_2/data_table_2.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_bloc/flutter_bloc.dart';
6+
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/archived_headlines/archived_headlines_bloc.dart';
7+
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
8+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
9+
import 'package:intl/intl.dart';
10+
import 'package:ui_kit/ui_kit.dart';
11+
12+
class ArchivedHeadlinesPage extends StatelessWidget {
13+
const ArchivedHeadlinesPage({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return BlocProvider(
18+
create: (context) => ArchivedHeadlinesBloc(
19+
headlinesRepository: context.read<DataRepository<Headline>>(),
20+
)..add(const LoadArchivedHeadlinesRequested(limit: kDefaultRowsPerPage)),
21+
child: const _ArchivedHeadlinesView(),
22+
);
23+
}
24+
}
25+
26+
class _ArchivedHeadlinesView extends StatelessWidget {
27+
const _ArchivedHeadlinesView();
28+
29+
@override
30+
Widget build(BuildContext context) {
31+
final l10n = AppLocalizationsX(context).l10n;
32+
return Scaffold(
33+
appBar: AppBar(
34+
title: Text(l10n.archivedHeadlines),
35+
),
36+
body: Padding(
37+
padding: const EdgeInsets.all(AppSpacing.lg),
38+
child: BlocBuilder<ArchivedHeadlinesBloc, ArchivedHeadlinesState>(
39+
builder: (context, state) {
40+
if (state.status == ArchivedHeadlinesStatus.loading &&
41+
state.headlines.isEmpty) {
42+
return LoadingStateWidget(
43+
icon: Icons.newspaper,
44+
headline: l10n.loadingArchivedHeadlines,
45+
subheadline: l10n.pleaseWait,
46+
);
47+
}
48+
49+
if (state.status == ArchivedHeadlinesStatus.failure) {
50+
return FailureStateWidget(
51+
exception: state.exception!,
52+
onRetry: () => context.read<ArchivedHeadlinesBloc>().add(
53+
const LoadArchivedHeadlinesRequested(
54+
limit: kDefaultRowsPerPage,
55+
),
56+
),
57+
);
58+
}
59+
60+
if (state.headlines.isEmpty) {
61+
return Center(child: Text(l10n.noArchivedHeadlinesFound));
62+
}
63+
64+
return Column(
65+
children: [
66+
if (state.status == ArchivedHeadlinesStatus.loading &&
67+
state.headlines.isNotEmpty)
68+
const LinearProgressIndicator(),
69+
Expanded(
70+
child: PaginatedDataTable2(
71+
columns: [
72+
DataColumn2(
73+
label: Text(l10n.headlineTitle),
74+
size: ColumnSize.L,
75+
),
76+
DataColumn2(
77+
label: Text(l10n.sourceName),
78+
size: ColumnSize.M,
79+
),
80+
DataColumn2(
81+
label: Text(l10n.lastUpdated),
82+
size: ColumnSize.M,
83+
),
84+
DataColumn2(
85+
label: Text(l10n.actions),
86+
size: ColumnSize.S,
87+
fixedWidth: 120,
88+
),
89+
],
90+
source: _HeadlinesDataSource(
91+
context: context,
92+
headlines: state.headlines,
93+
hasMore: state.hasMore,
94+
l10n: l10n,
95+
),
96+
rowsPerPage: kDefaultRowsPerPage,
97+
availableRowsPerPage: const [kDefaultRowsPerPage],
98+
onPageChanged: (pageIndex) {
99+
final newOffset = pageIndex * kDefaultRowsPerPage;
100+
if (newOffset >= state.headlines.length &&
101+
state.hasMore &&
102+
state.status != ArchivedHeadlinesStatus.loading) {
103+
context.read<ArchivedHeadlinesBloc>().add(
104+
LoadArchivedHeadlinesRequested(
105+
startAfterId: state.cursor,
106+
limit: kDefaultRowsPerPage,
107+
),
108+
);
109+
}
110+
},
111+
empty: Center(child: Text(l10n.noHeadlinesFound)),
112+
showCheckboxColumn: false,
113+
showFirstLastButtons: true,
114+
fit: FlexFit.tight,
115+
headingRowHeight: 56,
116+
dataRowHeight: 56,
117+
columnSpacing: AppSpacing.md,
118+
horizontalMargin: AppSpacing.md,
119+
),
120+
),
121+
],
122+
);
123+
},
124+
),
125+
),
126+
);
127+
}
128+
}
129+
130+
class _HeadlinesDataSource extends DataTableSource {
131+
_HeadlinesDataSource({
132+
required this.context,
133+
required this.headlines,
134+
required this.hasMore,
135+
required this.l10n,
136+
});
137+
138+
final BuildContext context;
139+
final List<Headline> headlines;
140+
final bool hasMore;
141+
final AppLocalizations l10n;
142+
143+
@override
144+
DataRow? getRow(int index) {
145+
if (index >= headlines.length) {
146+
return null;
147+
}
148+
final headline = headlines[index];
149+
return DataRow2(
150+
cells: [
151+
DataCell(
152+
Text(
153+
headline.title,
154+
maxLines: 2,
155+
overflow: TextOverflow.ellipsis,
156+
),
157+
),
158+
DataCell(Text(headline.source.name)),
159+
DataCell(
160+
Text(
161+
DateFormat('dd-MM-yyyy').format(headline.updatedAt.toLocal()),
162+
),
163+
),
164+
DataCell(
165+
Row(
166+
children: [
167+
IconButton(
168+
icon: const Icon(Icons.restore),
169+
tooltip: l10n.restore,
170+
onPressed: () {
171+
context.read<ArchivedHeadlinesBloc>().add(
172+
RestoreHeadlineRequested(headline.id),
173+
);
174+
},
175+
),
176+
IconButton(
177+
icon: const Icon(Icons.delete_forever),
178+
tooltip: l10n.deleteForever,
179+
onPressed: () {
180+
context.read<ArchivedHeadlinesBloc>().add(
181+
DeleteHeadlineForeverRequested(headline.id),
182+
);
183+
},
184+
),
185+
],
186+
),
187+
),
188+
],
189+
);
190+
}
191+
192+
@override
193+
bool get isRowCountApproximate => hasMore;
194+
195+
@override
196+
int get rowCount => headlines.length;
197+
198+
@override
199+
int get selectedRowCount => 0;
200+
}

0 commit comments

Comments
 (0)