import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../../sample/promo_sample_data.dart'; import '../../components/delegate/filter_bar_delegate.dart'; import '../../router/app_router.gr.dart'; import 'promo_ui.dart'; import 'widgets/promo_banner.dart'; import 'widgets/promo_card.dart'; enum _PromoFilter { all('Semua'), point('EnakPoint'), coin('EnakCoin'), reward('Hadiah'); const _PromoFilter(this.label); final String label; bool matches(Promo promo) => switch (this) { _PromoFilter.all => true, _PromoFilter.point => promo.type == PromoType.point, _PromoFilter.coin => promo.type == PromoType.coin, _PromoFilter.reward => promo.type == PromoType.reward, }; } @RoutePage() class PromoPage extends StatefulWidget { const PromoPage({super.key}); @override State createState() => _PromoPageState(); } class _PromoPageState extends State { final PageController _bannerController = PageController( viewportFraction: 0.9, ); int _bannerIndex = 0; _PromoFilter _filter = _PromoFilter.all; /// Promo yang belum berakhir, paling cepat berakhir di atas. final List _promos = promoSamples.where((p) => p.endDate.isAfter(DateTime.now())).toList() ..sort((a, b) => a.endDate.compareTo(b.endDate)); List get _featured => _promos.where((p) => p.isFeatured && !isPromoUpcoming(p)).toList(); int _countOf(_PromoFilter filter) => _promos.where(filter.matches).length; void _open(Promo promo) => context.router.push(PromoDetailRoute(promo: promo)); @override void dispose() { _bannerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final visible = _promos.where(_filter.matches).toList(); final ongoing = visible.where((p) => !isPromoUpcoming(p)).toList(); final upcoming = visible.where(isPromoUpcoming).toList(); final featured = _featured; return Scaffold( backgroundColor: AppColor.background, appBar: AppBar(title: const Text('EnakPromo')), body: CustomScrollView( slivers: [ if (featured.isNotEmpty) SliverToBoxAdapter(child: _buildBanners(featured)), SliverToBoxAdapter(child: _buildHowItWorks()), SliverPersistentHeader( pinned: true, delegate: FilterBarDelegate<_PromoFilter>( values: _PromoFilter.values, selected: _filter, labelOf: (filter) => filter.label, countOf: _countOf, onSelected: (filter) => setState(() => _filter = filter), ), ), if (visible.isEmpty) SliverFillRemaining(hasScrollBody: false, child: _buildEmpty()) else ...[ if (ongoing.isNotEmpty) ..._buildSection('Sedang berlangsung', ongoing), if (upcoming.isNotEmpty) ..._buildSection('Segera hadir', upcoming), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ], ), ); } Widget _buildBanners(List featured) { return Column( children: [ const SizedBox(height: 16), SizedBox( // Ikut skala font sistem supaya teks banner tidak terpotong. height: MediaQuery.textScalerOf(context).scale(168), child: PageView.builder( controller: _bannerController, itemCount: featured.length, onPageChanged: (index) => setState(() => _bannerIndex = index), itemBuilder: (context, index) { final promo = featured[index]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: PromoBanner(promo: promo, onTap: () => _open(promo)), ); }, ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(featured.length, (index) { final isActive = index == _bannerIndex; return AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.symmetric(horizontal: 3), width: isActive ? 18 : 6, height: 6, decoration: BoxDecoration( color: isActive ? AppColor.primary : AppColor.border, borderRadius: BorderRadius.circular(3), ), ); }), ), ], ); } Widget _buildHowItWorks() { return Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 4), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColor.white, borderRadius: BorderRadius.circular(AppValue.borderRadius), border: Border.all(color: AppColor.borderLight), ), child: Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColor.primary.withOpacity(0.08), shape: BoxShape.circle, ), child: const Icon( Icons.phone_iphone_rounded, size: 18, color: AppColor.primary, ), ), const SizedBox(width: 12), Expanded( child: Text.rich( TextSpan( children: [ TextSpan( text: 'Tanpa klaim. ', style: AppStyle.sm.copyWith(fontWeight: FontWeight.w700), ), const TextSpan( text: 'Sebutkan nomor HP akunmu ke kasir saat bayar, bonus ' 'promo masuk otomatis.', ), ], ), style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ), ], ), ); } List _buildSection(String title, List promos) { return [ SliverPadding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 10), sliver: SliverToBoxAdapter( child: Text( title, style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), ), ), ), SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16), sliver: SliverList.separated( itemCount: promos.length, separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (context, index) { final promo = promos[index]; return PromoCard(promo: promo, onTap: () => _open(promo)); }, ), ), const SliverToBoxAdapter(child: SizedBox(height: 16)), ]; } Widget _buildEmpty() { return Padding( padding: const EdgeInsets.fromLTRB(32, 32, 32, 64), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 96, height: 96, alignment: Alignment.center, decoration: BoxDecoration( color: AppColor.primary.withOpacity(0.06), shape: BoxShape.circle, ), child: const Text('🏷️', style: TextStyle(fontSize: 44)), ), const SizedBox(height: 16), Text( _filter == _PromoFilter.all ? 'Belum ada promo' : 'Belum ada promo ${_filter.label}', textAlign: TextAlign.center, style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 6), Text( 'Promo baru akan muncul di sini.', textAlign: TextAlign.center, style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ], ), ); } }