Files
enaklo-flutter/lib/presentation/pages/news/news_page.dart
T
efrilmandClaude Opus 5.5 5ba60747a8 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>
2026-10-01 02:26:43 +07:00

136 lines
4.2 KiB
Dart

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),
),
],
),
);
}
}