EnakNews: halaman berita dan detail berita
- Filter kategori, berita utama, dan daftar artikel terbaru - Detail artikel dengan sampul, penulis, isi, dan "Baca juga" - Data contoh 8 artikel; artikel tips mengikuti aturan di docs - Jumlah pada AppFilterChip kini opsional Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c35d0eedbe
commit
5ba60747a8
@@ -0,0 +1,135 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../sample/news_sample_data.dart';
|
||||
import '../../components/delegate/filter_bar_delegate.dart';
|
||||
import '../../router/app_router.gr.dart';
|
||||
import 'widgets/news_headline_card.dart';
|
||||
import 'widgets/news_list_tile.dart';
|
||||
|
||||
@RoutePage()
|
||||
class NewsPage extends StatefulWidget {
|
||||
const NewsPage({super.key});
|
||||
|
||||
@override
|
||||
State<NewsPage> createState() => _NewsPageState();
|
||||
}
|
||||
|
||||
class _NewsPageState extends State<NewsPage> {
|
||||
/// `null` berarti semua kategori.
|
||||
static const List<NewsCategory?> _filters = [null, ...NewsCategory.values];
|
||||
|
||||
NewsCategory? _category;
|
||||
|
||||
/// Terbaru di atas.
|
||||
final List<NewsArticle> _articles = newsSamples
|
||||
..sort((a, b) => b.publishedAt.compareTo(a.publishedAt));
|
||||
|
||||
void _open(NewsArticle article) =>
|
||||
context.router.push(NewsDetailRoute(article: article));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final visible = _articles
|
||||
.where((a) => _category == null || a.category == _category)
|
||||
.toList();
|
||||
// Berita utama hanya di "Semua"; di kategori lain semua jadi daftar.
|
||||
final headline = _category == null
|
||||
? visible.where((a) => a.isHeadline).firstOrNull
|
||||
: null;
|
||||
final rest = visible.where((a) => a != headline).toList();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: AppBar(title: const Text('EnakNews')),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: FilterBarDelegate<NewsCategory?>(
|
||||
values: _filters,
|
||||
selected: _category,
|
||||
labelOf: (category) => category?.label ?? 'Semua',
|
||||
onSelected: (category) => setState(() => _category = category),
|
||||
),
|
||||
),
|
||||
if (headline != null)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 20),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: NewsHeadlineCard(
|
||||
article: headline,
|
||||
onTap: () => _open(headline),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (visible.isEmpty)
|
||||
SliverFillRemaining(hasScrollBody: false, child: _buildEmpty())
|
||||
else if (rest.isNotEmpty) ...[
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 10),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
_category?.label ?? 'Terbaru',
|
||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(child: _buildList(rest)),
|
||||
),
|
||||
],
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildList(List<NewsArticle> articles) {
|
||||
return Material(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < articles.length; i++) ...[
|
||||
if (i > 0)
|
||||
const Divider(
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: AppColor.borderLight,
|
||||
),
|
||||
NewsListTile(article: articles[i], onTap: () => _open(articles[i])),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmpty() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(32, 32, 32, 64),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('📰', style: TextStyle(fontSize: 44)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Belum ada berita',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Berita terbaru dari outlet akan muncul di sini.',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user