- 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>
136 lines
4.2 KiB
Dart
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),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|