diff --git a/lib/presentation/components/chip/app_filter_chip.dart b/lib/presentation/components/chip/app_filter_chip.dart new file mode 100644 index 0000000..c16755a --- /dev/null +++ b/lib/presentation/components/chip/app_filter_chip.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +/// Chip filter berbentuk pil dengan jumlah item di sebelah label. +class AppFilterChip extends StatelessWidget { + const AppFilterChip({ + super.key, + required this.label, + required this.count, + required this.isSelected, + required this.onTap, + }); + + final String label; + final int count; + final bool isSelected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final foreground = isSelected ? AppColor.textWhite : AppColor.textPrimary; + + return Material( + color: isSelected ? AppColor.primary : AppColor.white, + shape: StadiumBorder( + side: BorderSide( + color: isSelected ? AppColor.primary : AppColor.border, + ), + ), + child: InkWell( + customBorder: const StadiumBorder(), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: AppStyle.sm.copyWith( + color: foreground, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: isSelected + ? AppColor.white.withOpacity(0.2) + : AppColor.primary.withOpacity(0.08), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '$count', + style: AppStyle.xs.copyWith( + color: isSelected ? AppColor.textWhite : AppColor.primary, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/components/delegate/filter_bar_delegate.dart b/lib/presentation/components/delegate/filter_bar_delegate.dart new file mode 100644 index 0000000..592dc15 --- /dev/null +++ b/lib/presentation/components/delegate/filter_bar_delegate.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import '../chip/app_filter_chip.dart'; + +/// Baris [AppFilterChip] yang bisa di-scroll menyamping, untuk +/// [SliverPersistentHeader] yang menempel di atas daftar. +class FilterBarDelegate extends SliverPersistentHeaderDelegate { + FilterBarDelegate({ + required this.values, + required this.selected, + required this.labelOf, + required this.countOf, + required this.onSelected, + this.backgroundColor = AppColor.background, + }); + + final List values; + final T selected; + final String Function(T value) labelOf; + final int Function(T value) countOf; + final ValueChanged onSelected; + final Color backgroundColor; + + static const double _height = 56; + + @override + double get minExtent => _height; + + @override + double get maxExtent => _height; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Container( + color: backgroundColor, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + itemCount: values.length, + separatorBuilder: (_, _) => const SizedBox(width: 8), + itemBuilder: (context, index) { + final value = values[index]; + return AppFilterChip( + label: labelOf(value), + count: countOf(value), + isSelected: value == selected, + onTap: () => onSelected(value), + ); + }, + ), + ); + } + + @override + bool shouldRebuild(covariant FilterBarDelegate oldDelegate) => + oldDelegate.selected != selected || oldDelegate.values != values; +} diff --git a/lib/presentation/pages/main/pages/home/widgets/service_section.dart b/lib/presentation/pages/main/pages/home/widgets/service_section.dart index 6e7e2d7..68c1959 100644 --- a/lib/presentation/pages/main/pages/home/widgets/service_section.dart +++ b/lib/presentation/pages/main/pages/home/widgets/service_section.dart @@ -57,10 +57,7 @@ class HomeServiceSection extends StatelessWidget { _HomeMenu( label: 'EnakPromo', icon: Assets.icons.enakpromoIcon, - route: ComingSoonRoute( - title: 'EnakPromo', - iconPath: Assets.icons.enakpromoIcon.path, - ), + route: const PromoRoute(), ), _HomeMenu( label: 'EnakShop', diff --git a/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart b/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart new file mode 100644 index 0000000..9705674 --- /dev/null +++ b/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart @@ -0,0 +1,359 @@ +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/button/button.dart'; +import '../../../../router/app_router.gr.dart'; +import '../../promo_ui.dart'; + +@RoutePage() +class PromoDetailPage extends StatelessWidget { + const PromoDetailPage({super.key, required this.promo}); + + final Promo promo; + + @override + Widget build(BuildContext context) { + final isReward = promo.type == PromoType.reward; + + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: const Text('Detail Promo')), + body: ListView( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), + children: [ + _buildHero(), + if (isPromoUpcoming(promo)) ...[ + const SizedBox(height: 12), + _buildUpcomingNote(), + ], + const SizedBox(height: 12), + _Section( + child: Column( + children: [ + _InfoRow( + icon: Icons.card_giftcard_rounded, + label: 'Hadiah', + value: promo.rewardLabel, + ), + const Divider(height: 20, color: AppColor.borderLight), + _InfoRow( + icon: Icons.calendar_month_rounded, + label: 'Periode', + value: promoPeriodLabel(promo), + trailing: PromoTimeBadge(promo: promo), + ), + const Divider(height: 20, color: AppColor.borderLight), + _InfoRow( + icon: Icons.storefront_outlined, + label: 'Berlaku di', + value: promoOutletLabel(promo), + ), + ], + ), + ), + const SizedBox(height: 12), + _Section( + title: 'Tentang promo', + child: Text( + promo.description, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + ), + const SizedBox(height: 12), + _Section( + title: 'Cara dapat', + child: Column( + children: [ + for (var i = 0; i < promo.steps.length; i++) + _StepRow(number: i + 1, text: promo.steps[i]), + ], + ), + ), + if (promo.terms.isNotEmpty) ...[ + const SizedBox(height: 12), + _Section( + title: 'Syarat & Ketentuan', + child: Column( + children: [ + for (final term in promo.terms) _BulletRow(text: term), + ], + ), + ), + ], + ], + ), + bottomNavigationBar: Container( + decoration: const BoxDecoration( + color: AppColor.white, + border: Border(top: BorderSide(color: AppColor.borderLight)), + ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + child: AppElevatedButton( + title: isReward ? 'Lihat Reward Saya' : 'Cari Outlet', + onPressed: () => context.router.push( + isReward ? const RewardRoute() : const OutletRoute(), + ), + ), + ), + ), + ), + ); + } + + Widget _buildHero() { + final gradient = promo.type.gradient; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + colors: gradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: gradient.last.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 6), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + promo.rewardLabel, + style: AppStyle.xs.copyWith( + color: promo.type.color, + fontWeight: FontWeight.w800, + ), + ), + ), + const SizedBox(height: 10), + Text( + promo.title, + style: AppStyle.h6.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w800, + height: 1.2, + ), + ), + const SizedBox(height: 6), + Text( + promo.subtitle, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + ], + ), + ), + const SizedBox(width: 12), + PromoTypeIcon(type: promo.type, size: 80), + ], + ), + ); + } + + Widget _buildUpcomingNote() { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.info.withOpacity(0.08), + borderRadius: BorderRadius.circular(AppValue.borderRadius), + ), + child: Row( + children: [ + const Icon(Icons.info_outline, size: 20, color: AppColor.info), + const SizedBox(width: 10), + Expanded( + child: Text( + 'Promo ini mulai ${formatPromoDate(promo.startDate)}. Belanja ' + 'sebelum tanggal itu belum mendapat bonus.', + style: AppStyle.sm.copyWith(color: AppColor.textPrimary), + ), + ), + ], + ), + ); + } +} + +class _Section extends StatelessWidget { + const _Section({this.title, required this.child}); + + final String? title; + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) ...[ + Text( + title!, + style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 12), + ], + child, + ], + ), + ); + } +} + +class _InfoRow extends StatelessWidget { + const _InfoRow({ + required this.icon, + required this.label, + required this.value, + this.trailing, + }); + + final IconData icon; + final String label; + final String value; + final Widget? trailing; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, size: 18, color: AppColor.textSecondary), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: AppStyle.xs.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 2), + Text( + value, + style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + ), + ], + ), + ), + if (trailing != null) ...[const SizedBox(width: 8), trailing!], + ], + ); + } +} + +class _StepRow extends StatelessWidget { + const _StepRow({required this.number, required this.text}); + + final int number; + final String text; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 22, + height: 22, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Text( + '$number', + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w800, + ), + ), + ), + const SizedBox(width: 10), + Expanded(child: Text(text, style: AppStyle.md.copyWith(height: 1.4))), + ], + ), + ); + } +} + +class _BulletRow extends StatelessWidget { + const _BulletRow({required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 7), + child: Container( + width: 5, + height: 5, + decoration: const BoxDecoration( + color: AppColor.textSecondary, + shape: BoxShape.circle, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + text, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/promo/promo_page.dart b/lib/presentation/pages/promo/promo_page.dart new file mode 100644 index 0000000..46b636a --- /dev/null +++ b/lib/presentation/pages/promo/promo_page.dart @@ -0,0 +1,250 @@ +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( + height: 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), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/promo/promo_ui.dart b/lib/presentation/pages/promo/promo_ui.dart new file mode 100644 index 0000000..e63a93f --- /dev/null +++ b/lib/presentation/pages/promo/promo_ui.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../../common/theme/theme.dart'; +import '../../../domain/wallet/wallet.dart'; +import '../../../sample/promo_sample_data.dart'; +import '../../components/assets/assets.gen.dart'; +import '../wallet/wallet_ui.dart'; + +/// Promo aktif dengan sisa hari segini dianggap segera berakhir. +const int promoEndingSoonDays = 3; + +extension PromoTypeUi on PromoType { + String get label => switch (this) { + PromoType.point => 'EnakPoint', + PromoType.coin => 'EnakCoin', + PromoType.reward => 'Hadiah', + }; + + /// Warna aksen untuk teks dan latar tipis; cukup gelap di atas putih. + Color get color => switch (this) { + PromoType.point => AppColor.primary, + PromoType.coin => const Color(0xFFC26A00), + PromoType.reward => AppColor.secondaryDark, + }; + + /// Latar banner; teks putih di atasnya. + List get gradient => switch (this) { + PromoType.point => AppColor.primaryGradient, + PromoType.coin => const [Color(0xFFF59E0B), Color(0xFFC2410C)], + PromoType.reward => const [Color(0xFF43A047), Color(0xFF1B5E20)], + }; +} + +bool isPromoUpcoming(Promo promo) => promo.startDate.isAfter(DateTime.now()); + +/// Sisa hari kalender sampai [Promo.endDate]; 0 berarti berakhir hari ini. +int promoDaysLeft(Promo promo) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final end = DateTime( + promo.endDate.year, + promo.endDate.month, + promo.endDate.day, + ); + return end.difference(today).inDays; +} + +bool isPromoEndingSoon(Promo promo) => + !isPromoUpcoming(promo) && promoDaysLeft(promo) <= promoEndingSoonDays; + +/// "12 Okt". +String formatPromoDate(DateTime date) => + DateFormat('d MMM', 'id_ID').format(date); + +/// "1 Okt – 25 Okt 2026". +String promoPeriodLabel(Promo promo) => + '${formatPromoDate(promo.startDate)} – ' + '${DateFormat('d MMM yyyy', 'id_ID').format(promo.endDate)}'; + +String promoOutletLabel(Promo promo) => + promo.outlets.isEmpty ? 'Semua outlet' : promo.outlets.join(', '); + +/// "Mulai 4 Okt" / "Berakhir 2 hari lagi" / "s/d 25 Okt". +String promoTimeLabel(Promo promo) { + if (isPromoUpcoming(promo)) { + return 'Mulai ${formatPromoDate(promo.startDate)}'; + } + final daysLeft = promoDaysLeft(promo); + if (daysLeft <= 0) return 'Berakhir hari ini'; + if (daysLeft <= promoEndingSoonDays) return 'Berakhir $daysLeft hari lagi'; + return 's/d ${formatPromoDate(promo.endDate)}'; +} + +/// Ikon 3D sesuai hadiah promo. +class PromoTypeIcon extends StatelessWidget { + const PromoTypeIcon({super.key, required this.type, this.size = 32}); + + final PromoType type; + final double size; + + @override + Widget build(BuildContext context) { + return switch (type) { + PromoType.point => WalletCurrencyIcon( + currency: WalletCurrency.point, + size: size, + ), + PromoType.coin => WalletCurrencyIcon( + currency: WalletCurrency.coin, + size: size, + ), + PromoType.reward => Assets.icons.enakrewardIcon.image( + width: size, + height: size, + fit: BoxFit.contain, + // Aset aslinya besar; decode seukuran tampilan saja. + cacheWidth: (size * MediaQuery.devicePixelRatioOf(context)).round(), + ), + }; + } +} + +/// Label waktu promo: biru bila belum mulai, oranye bila segera berakhir. +class PromoTimeBadge extends StatelessWidget { + const PromoTimeBadge({super.key, required this.promo}); + + final Promo promo; + + @override + Widget build(BuildContext context) { + final color = isPromoUpcoming(promo) + ? AppColor.info + : isPromoEndingSoon(promo) + ? AppColor.warning + : AppColor.textSecondary; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.schedule, size: 12, color: color), + const SizedBox(width: 4), + Text( + promoTimeLabel(promo), + style: AppStyle.xs.copyWith( + color: color, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/promo/widgets/promo_banner.dart b/lib/presentation/pages/promo/widgets/promo_banner.dart new file mode 100644 index 0000000..b638a98 --- /dev/null +++ b/lib/presentation/pages/promo/widgets/promo_banner.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/promo_sample_data.dart'; +import '../promo_ui.dart'; + +/// Kartu besar untuk promo unggulan di bagian atas EnakPromo. +class PromoBanner extends StatelessWidget { + const PromoBanner({super.key, required this.promo, required this.onTap}); + + final Promo promo; + final VoidCallback onTap; + + static const double _iconSize = 96; + + @override + Widget build(BuildContext context) { + final gradient = promo.type.gradient; + + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + colors: gradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: gradient.last.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 6), + ), + ], + ), + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: onTap, + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Stack( + children: [ + // Lingkaran dekoratif di belakang ikon. + Positioned( + right: -36, + top: -36, + child: _Circle(size: 150, opacity: 0.1), + ), + Positioned( + right: 40, + bottom: -48, + child: _Circle(size: 100, opacity: 0.08), + ), + Positioned( + right: 14, + bottom: 14, + child: PromoTypeIcon(type: promo.type, size: _iconSize), + ), + Padding( + padding: const EdgeInsets.fromLTRB( + 18, + 16, + _iconSize + 20, + 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Pill(text: promo.rewardLabel, color: promo.type.color), + const SizedBox(height: 8), + Text( + promo.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.xl.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w800, + height: 1.2, + ), + ), + const SizedBox(height: 4), + Text( + promo.subtitle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + const Spacer(), + Row( + children: [ + Icon( + Icons.schedule, + size: 14, + color: AppColor.textWhite.withOpacity(0.9), + ), + const SizedBox(width: 4), + Text( + promoTimeLabel(promo), + style: AppStyle.xs.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _Pill extends StatelessWidget { + const _Pill({required this.text, required this.color}); + + final String text; + final Color color; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + text, + style: AppStyle.xs.copyWith(color: color, fontWeight: FontWeight.w800), + ), + ); + } +} + +class _Circle extends StatelessWidget { + const _Circle({required this.size, required this.opacity}); + + final double size; + final double opacity; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(opacity), + shape: BoxShape.circle, + ), + ); + } +} diff --git a/lib/presentation/pages/promo/widgets/promo_card.dart b/lib/presentation/pages/promo/widgets/promo_card.dart new file mode 100644 index 0000000..e6e8fc5 --- /dev/null +++ b/lib/presentation/pages/promo/widgets/promo_card.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/promo_sample_data.dart'; +import '../promo_ui.dart'; + +class PromoCard extends StatelessWidget { + const PromoCard({super.key, required this.promo, required this.onTap}); + + final Promo promo; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final color = promo.type.color; + + return Material( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + shadowColor: Colors.black.withOpacity(0.25), + elevation: 1, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 56, + height: 56, + alignment: Alignment.center, + decoration: BoxDecoration( + color: color.withOpacity(0.08), + borderRadius: BorderRadius.circular(14), + ), + child: PromoTypeIcon(type: promo.type, size: 34), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + promo.rewardLabel, + style: AppStyle.xs.copyWith( + color: color, + fontWeight: FontWeight.w800, + ), + ), + ), + const SizedBox(height: 6), + Text( + promo.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 2), + Text( + promo.subtitle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + const SizedBox(height: 10), + // Wrap: di layar sempit label waktu turun ke baris baru + // supaya nama outlet tidak terpotong. + Wrap( + spacing: 8, + runSpacing: 6, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.storefront_outlined, + size: 14, + color: AppColor.textSecondary, + ), + const SizedBox(width: 4), + Flexible( + child: Text( + promoOutletLabel(promo), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + PromoTimeBadge(promo: promo), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/reward/reward_page.dart b/lib/presentation/pages/reward/reward_page.dart index bbe1cd0..c7c4c02 100644 --- a/lib/presentation/pages/reward/reward_page.dart +++ b/lib/presentation/pages/reward/reward_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../../sample/reward_sample_data.dart'; import '../../components/assets/assets.gen.dart'; +import '../../components/delegate/filter_bar_delegate.dart'; import '../../router/app_router.gr.dart'; import 'reward_ui.dart'; import 'widgets/reward_card.dart'; @@ -65,8 +66,10 @@ class _RewardPageState extends State { SliverToBoxAdapter(child: _buildSummary()), SliverPersistentHeader( pinned: true, - delegate: _FilterHeaderDelegate( + delegate: FilterBarDelegate<_RewardFilter>( + values: _RewardFilter.values, selected: _filter, + labelOf: (filter) => filter.label, countOf: _countOf, onSelected: (filter) => setState(() => _filter = filter), ), @@ -292,117 +295,3 @@ class _RewardPageState extends State { ); } } - -class _FilterHeaderDelegate extends SliverPersistentHeaderDelegate { - _FilterHeaderDelegate({ - required this.selected, - required this.countOf, - required this.onSelected, - }); - - final _RewardFilter selected; - final int Function(_RewardFilter) countOf; - final ValueChanged<_RewardFilter> onSelected; - - static const double _height = 56; - - @override - double get minExtent => _height; - - @override - double get maxExtent => _height; - - @override - Widget build( - BuildContext context, - double shrinkOffset, - bool overlapsContent, - ) { - return Container( - color: AppColor.background, - child: ListView.separated( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - itemCount: _RewardFilter.values.length, - separatorBuilder: (_, _) => const SizedBox(width: 8), - itemBuilder: (context, index) { - final filter = _RewardFilter.values[index]; - return _FilterChip( - label: filter.label, - count: countOf(filter), - isSelected: filter == selected, - onTap: () => onSelected(filter), - ); - }, - ), - ); - } - - @override - bool shouldRebuild(covariant _FilterHeaderDelegate oldDelegate) => - oldDelegate.selected != selected; -} - -class _FilterChip extends StatelessWidget { - const _FilterChip({ - required this.label, - required this.count, - required this.isSelected, - required this.onTap, - }); - - final String label; - final int count; - final bool isSelected; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final foreground = isSelected ? AppColor.textWhite : AppColor.textPrimary; - - return Material( - color: isSelected ? AppColor.primary : AppColor.white, - shape: StadiumBorder( - side: BorderSide( - color: isSelected ? AppColor.primary : AppColor.border, - ), - ), - child: InkWell( - customBorder: const StadiumBorder(), - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 14), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - label, - style: AppStyle.sm.copyWith( - color: foreground, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(width: 6), - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), - decoration: BoxDecoration( - color: isSelected - ? AppColor.white.withOpacity(0.2) - : AppColor.primary.withOpacity(0.08), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - '$count', - style: AppStyle.xs.copyWith( - color: isSelected ? AppColor.textWhite : AppColor.primary, - fontWeight: FontWeight.w700, - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 4360c99..bda0db9 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -83,6 +83,10 @@ class AppRouter extends RootStackRouter { // Reward AutoRoute(page: RewardRoute.page), + // Promo + AutoRoute(page: PromoRoute.page), + AutoRoute(page: PromoDetailRoute.page), + // Account AutoRoute(page: AccountMyRoute.page), AutoRoute(page: AddressRoute.page), diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index e9bcfe1..c0032a9 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -10,8 +10,8 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i50; -import 'package:enaklo/domain/wallet/wallet.dart' as _i54; +import 'package:auto_route/auto_route.dart' as _i52; +import 'package:enaklo/domain/wallet/wallet.dart' as _i56; import 'package:enaklo/presentation/pages/account/account_my/account_my_page.dart' as _i1; import 'package:enaklo/presentation/pages/account/address/address_page.dart' @@ -26,7 +26,7 @@ import 'package:enaklo/presentation/pages/auth/password/password_page.dart' as _i30; import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i34; import 'package:enaklo/presentation/pages/auth/register/register_page.dart' - as _i40; + as _i42; import 'package:enaklo/presentation/pages/coin/coin_page.dart' as _i4; import 'package:enaklo/presentation/pages/coin/pages/coin_history_page.dart' as _i3; @@ -58,7 +58,7 @@ import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' as _i39; import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' - as _i45; + as _i47; import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i22; import 'package:enaklo/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart' as _i21; @@ -77,33 +77,37 @@ import 'package:enaklo/presentation/pages/pin/pin_change_page.dart' as _i33; import 'package:enaklo/presentation/pages/pin/pin_settings_page.dart' as _i35; import 'package:enaklo/presentation/pages/pin/pin_setup_page.dart' as _i36; import 'package:enaklo/presentation/pages/point/point_page.dart' as _i37; -import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i41; -import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i42; +import 'package:enaklo/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart' + as _i40; +import 'package:enaklo/presentation/pages/promo/promo_page.dart' as _i41; +import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i43; +import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i44; import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' - as _i44; + as _i46; import 'package:enaklo/presentation/pages/wallet/exchange_page.dart' as _i16; import 'package:enaklo/presentation/pages/wallet/payment_code_page.dart' as _i31; -import 'package:enaklo/presentation/pages/wallet/transfer_page.dart' as _i43; +import 'package:enaklo/presentation/pages/wallet/transfer_page.dart' as _i45; import 'package:enaklo/presentation/pages/wallet/wallet_expiring_page.dart' - as _i46; + as _i48; import 'package:enaklo/presentation/pages/wallet/wallet_history_page.dart' - as _i47; -import 'package:enaklo/presentation/pages/wallet/wallet_page.dart' as _i48; -import 'package:enaklo/presentation/pages/webview/webview_page.dart' as _i49; -import 'package:enaklo/sample/community_sample_data.dart' as _i52; -import 'package:enaklo/sample/sample_data.dart' as _i53; -import 'package:flutter/material.dart' as _i51; + as _i49; +import 'package:enaklo/presentation/pages/wallet/wallet_page.dart' as _i50; +import 'package:enaklo/presentation/pages/webview/webview_page.dart' as _i51; +import 'package:enaklo/sample/community_sample_data.dart' as _i54; +import 'package:enaklo/sample/promo_sample_data.dart' as _i57; +import 'package:enaklo/sample/sample_data.dart' as _i55; +import 'package:flutter/material.dart' as _i53; /// generated route for /// [_i1.AccountMyPage] -class AccountMyRoute extends _i50.PageRouteInfo { - const AccountMyRoute({List<_i50.PageRouteInfo>? children}) +class AccountMyRoute extends _i52.PageRouteInfo { + const AccountMyRoute({List<_i52.PageRouteInfo>? children}) : super(AccountMyRoute.name, initialChildren: children); static const String name = 'AccountMyRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i1.AccountMyPage(); @@ -113,13 +117,13 @@ class AccountMyRoute extends _i50.PageRouteInfo { /// generated route for /// [_i2.AddressPage] -class AddressRoute extends _i50.PageRouteInfo { - const AddressRoute({List<_i50.PageRouteInfo>? children}) +class AddressRoute extends _i52.PageRouteInfo { + const AddressRoute({List<_i52.PageRouteInfo>? children}) : super(AddressRoute.name, initialChildren: children); static const String name = 'AddressRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i2.AddressPage(); @@ -129,13 +133,13 @@ class AddressRoute extends _i50.PageRouteInfo { /// generated route for /// [_i3.CoinHistoryPage] -class CoinHistoryRoute extends _i50.PageRouteInfo { - const CoinHistoryRoute({List<_i50.PageRouteInfo>? children}) +class CoinHistoryRoute extends _i52.PageRouteInfo { + const CoinHistoryRoute({List<_i52.PageRouteInfo>? children}) : super(CoinHistoryRoute.name, initialChildren: children); static const String name = 'CoinHistoryRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i3.CoinHistoryPage(); @@ -145,13 +149,13 @@ class CoinHistoryRoute extends _i50.PageRouteInfo { /// generated route for /// [_i4.CoinPage] -class CoinRoute extends _i50.PageRouteInfo { - const CoinRoute({List<_i50.PageRouteInfo>? children}) +class CoinRoute extends _i52.PageRouteInfo { + const CoinRoute({List<_i52.PageRouteInfo>? children}) : super(CoinRoute.name, initialChildren: children); static const String name = 'CoinRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i4.CoinPage(); @@ -161,12 +165,12 @@ class CoinRoute extends _i50.PageRouteInfo { /// generated route for /// [_i5.ComingSoonPage] -class ComingSoonRoute extends _i50.PageRouteInfo { +class ComingSoonRoute extends _i52.PageRouteInfo { ComingSoonRoute({ - _i51.Key? key, + _i53.Key? key, required String title, String? iconPath, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( ComingSoonRoute.name, args: ComingSoonRouteArgs(key: key, title: title, iconPath: iconPath), @@ -175,7 +179,7 @@ class ComingSoonRoute extends _i50.PageRouteInfo { static const String name = 'ComingSoonRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -191,7 +195,7 @@ class ComingSoonRoute extends _i50.PageRouteInfo { class ComingSoonRouteArgs { const ComingSoonRouteArgs({this.key, required this.title, this.iconPath}); - final _i51.Key? key; + final _i53.Key? key; final String title; @@ -218,11 +222,11 @@ class ComingSoonRouteArgs { /// generated route for /// [_i6.CommunityDetailPage] class CommunityDetailRoute - extends _i50.PageRouteInfo { + extends _i52.PageRouteInfo { CommunityDetailRoute({ - _i51.Key? key, - required _i52.CommunityGroup group, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + required _i54.CommunityGroup group, + List<_i52.PageRouteInfo>? children, }) : super( CommunityDetailRoute.name, args: CommunityDetailRouteArgs(key: key, group: group), @@ -231,7 +235,7 @@ class CommunityDetailRoute static const String name = 'CommunityDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -243,9 +247,9 @@ class CommunityDetailRoute class CommunityDetailRouteArgs { const CommunityDetailRouteArgs({this.key, required this.group}); - final _i51.Key? key; + final _i53.Key? key; - final _i52.CommunityGroup group; + final _i54.CommunityGroup group; @override String toString() { @@ -265,13 +269,13 @@ class CommunityDetailRouteArgs { /// generated route for /// [_i7.CommunityPage] -class CommunityRoute extends _i50.PageRouteInfo { - const CommunityRoute({List<_i50.PageRouteInfo>? children}) +class CommunityRoute extends _i52.PageRouteInfo { + const CommunityRoute({List<_i52.PageRouteInfo>? children}) : super(CommunityRoute.name, initialChildren: children); static const String name = 'CommunityRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i7.CommunityPage(); @@ -282,11 +286,11 @@ class CommunityRoute extends _i50.PageRouteInfo { /// generated route for /// [_i8.CommunityPostDetailPage] class CommunityPostDetailRoute - extends _i50.PageRouteInfo { + extends _i52.PageRouteInfo { CommunityPostDetailRoute({ - _i51.Key? key, - required _i52.CommunityPost post, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + required _i54.CommunityPost post, + List<_i52.PageRouteInfo>? children, }) : super( CommunityPostDetailRoute.name, args: CommunityPostDetailRouteArgs(key: key, post: post), @@ -295,7 +299,7 @@ class CommunityPostDetailRoute static const String name = 'CommunityPostDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -307,9 +311,9 @@ class CommunityPostDetailRoute class CommunityPostDetailRouteArgs { const CommunityPostDetailRouteArgs({this.key, required this.post}); - final _i51.Key? key; + final _i53.Key? key; - final _i52.CommunityPost post; + final _i54.CommunityPost post; @override String toString() { @@ -329,11 +333,11 @@ class CommunityPostDetailRouteArgs { /// generated route for /// [_i9.CreatePasswordPage] -class CreatePasswordRoute extends _i50.PageRouteInfo { +class CreatePasswordRoute extends _i52.PageRouteInfo { CreatePasswordRoute({ - _i51.Key? key, + _i53.Key? key, required String registrationToken, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( CreatePasswordRoute.name, args: CreatePasswordRouteArgs( @@ -345,11 +349,11 @@ class CreatePasswordRoute extends _i50.PageRouteInfo { static const String name = 'CreatePasswordRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( + return _i52.WrappedRoute( child: _i9.CreatePasswordPage( key: args.key, registrationToken: args.registrationToken, @@ -362,7 +366,7 @@ class CreatePasswordRoute extends _i50.PageRouteInfo { class CreatePasswordRouteArgs { const CreatePasswordRouteArgs({this.key, required this.registrationToken}); - final _i51.Key? key; + final _i53.Key? key; final String registrationToken; @@ -384,13 +388,13 @@ class CreatePasswordRouteArgs { /// generated route for /// [_i10.DrawDetailPage] -class DrawDetailRoute extends _i50.PageRouteInfo { - const DrawDetailRoute({List<_i50.PageRouteInfo>? children}) +class DrawDetailRoute extends _i52.PageRouteInfo { + const DrawDetailRoute({List<_i52.PageRouteInfo>? children}) : super(DrawDetailRoute.name, initialChildren: children); static const String name = 'DrawDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i10.DrawDetailPage(); @@ -400,13 +404,13 @@ class DrawDetailRoute extends _i50.PageRouteInfo { /// generated route for /// [_i11.DrawInfoPage] -class DrawInfoRoute extends _i50.PageRouteInfo { - const DrawInfoRoute({List<_i50.PageRouteInfo>? children}) +class DrawInfoRoute extends _i52.PageRouteInfo { + const DrawInfoRoute({List<_i52.PageRouteInfo>? children}) : super(DrawInfoRoute.name, initialChildren: children); static const String name = 'DrawInfoRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i11.DrawInfoPage(); @@ -416,13 +420,13 @@ class DrawInfoRoute extends _i50.PageRouteInfo { /// generated route for /// [_i12.DrawMyNumberPage] -class DrawMyNumberRoute extends _i50.PageRouteInfo { - const DrawMyNumberRoute({List<_i50.PageRouteInfo>? children}) +class DrawMyNumberRoute extends _i52.PageRouteInfo { + const DrawMyNumberRoute({List<_i52.PageRouteInfo>? children}) : super(DrawMyNumberRoute.name, initialChildren: children); static const String name = 'DrawMyNumberRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i12.DrawMyNumberPage(); @@ -432,13 +436,13 @@ class DrawMyNumberRoute extends _i50.PageRouteInfo { /// generated route for /// [_i13.DrawPage] -class DrawRoute extends _i50.PageRouteInfo { - const DrawRoute({List<_i50.PageRouteInfo>? children}) +class DrawRoute extends _i52.PageRouteInfo { + const DrawRoute({List<_i52.PageRouteInfo>? children}) : super(DrawRoute.name, initialChildren: children); static const String name = 'DrawRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i13.DrawPage(); @@ -448,13 +452,13 @@ class DrawRoute extends _i50.PageRouteInfo { /// generated route for /// [_i14.DrawTodayPage] -class DrawTodayRoute extends _i50.PageRouteInfo { - const DrawTodayRoute({List<_i50.PageRouteInfo>? children}) +class DrawTodayRoute extends _i52.PageRouteInfo { + const DrawTodayRoute({List<_i52.PageRouteInfo>? children}) : super(DrawTodayRoute.name, initialChildren: children); static const String name = 'DrawTodayRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i14.DrawTodayPage(); @@ -464,13 +468,13 @@ class DrawTodayRoute extends _i50.PageRouteInfo { /// generated route for /// [_i15.DrawWinnerPage] -class DrawWinnerRoute extends _i50.PageRouteInfo { - const DrawWinnerRoute({List<_i50.PageRouteInfo>? children}) +class DrawWinnerRoute extends _i52.PageRouteInfo { + const DrawWinnerRoute({List<_i52.PageRouteInfo>? children}) : super(DrawWinnerRoute.name, initialChildren: children); static const String name = 'DrawWinnerRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i15.DrawWinnerPage(); @@ -480,45 +484,45 @@ class DrawWinnerRoute extends _i50.PageRouteInfo { /// generated route for /// [_i16.ExchangePage] -class ExchangeRoute extends _i50.PageRouteInfo { - const ExchangeRoute({List<_i50.PageRouteInfo>? children}) +class ExchangeRoute extends _i52.PageRouteInfo { + const ExchangeRoute({List<_i52.PageRouteInfo>? children}) : super(ExchangeRoute.name, initialChildren: children); static const String name = 'ExchangeRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i16.ExchangePage()); + return _i52.WrappedRoute(child: const _i16.ExchangePage()); }, ); } /// generated route for /// [_i17.FerrisWheelPage] -class FerrisWheelRoute extends _i50.PageRouteInfo { - const FerrisWheelRoute({List<_i50.PageRouteInfo>? children}) +class FerrisWheelRoute extends _i52.PageRouteInfo { + const FerrisWheelRoute({List<_i52.PageRouteInfo>? children}) : super(FerrisWheelRoute.name, initialChildren: children); static const String name = 'FerrisWheelRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i17.FerrisWheelPage()); + return _i52.WrappedRoute(child: const _i17.FerrisWheelPage()); }, ); } /// generated route for /// [_i18.HomePage] -class HomeRoute extends _i50.PageRouteInfo { - const HomeRoute({List<_i50.PageRouteInfo>? children}) +class HomeRoute extends _i52.PageRouteInfo { + const HomeRoute({List<_i52.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i18.HomePage(); @@ -528,29 +532,29 @@ class HomeRoute extends _i50.PageRouteInfo { /// generated route for /// [_i19.LoginPage] -class LoginRoute extends _i50.PageRouteInfo { - const LoginRoute({List<_i50.PageRouteInfo>? children}) +class LoginRoute extends _i52.PageRouteInfo { + const LoginRoute({List<_i52.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i19.LoginPage()); + return _i52.WrappedRoute(child: const _i19.LoginPage()); }, ); } /// generated route for /// [_i20.MainPage] -class MainRoute extends _i50.PageRouteInfo { - const MainRoute({List<_i50.PageRouteInfo>? children}) +class MainRoute extends _i52.PageRouteInfo { + const MainRoute({List<_i52.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i20.MainPage(); @@ -560,11 +564,11 @@ class MainRoute extends _i50.PageRouteInfo { /// generated route for /// [_i21.MerchantDetailPage] -class MerchantDetailRoute extends _i50.PageRouteInfo { +class MerchantDetailRoute extends _i52.PageRouteInfo { MerchantDetailRoute({ - _i51.Key? key, - required _i53.MerchantModel merchant, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + required _i55.MerchantModel merchant, + List<_i52.PageRouteInfo>? children, }) : super( MerchantDetailRoute.name, args: MerchantDetailRouteArgs(key: key, merchant: merchant), @@ -573,7 +577,7 @@ class MerchantDetailRoute extends _i50.PageRouteInfo { static const String name = 'MerchantDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -585,9 +589,9 @@ class MerchantDetailRoute extends _i50.PageRouteInfo { class MerchantDetailRouteArgs { const MerchantDetailRouteArgs({this.key, required this.merchant}); - final _i51.Key? key; + final _i53.Key? key; - final _i53.MerchantModel merchant; + final _i55.MerchantModel merchant; @override String toString() { @@ -607,13 +611,13 @@ class MerchantDetailRouteArgs { /// generated route for /// [_i22.MerchantPage] -class MerchantRoute extends _i50.PageRouteInfo { - const MerchantRoute({List<_i50.PageRouteInfo>? children}) +class MerchantRoute extends _i52.PageRouteInfo { + const MerchantRoute({List<_i52.PageRouteInfo>? children}) : super(MerchantRoute.name, initialChildren: children); static const String name = 'MerchantRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i22.MerchantPage(); @@ -623,13 +627,13 @@ class MerchantRoute extends _i50.PageRouteInfo { /// generated route for /// [_i23.MisteryBoxPage] -class MisteryBoxRoute extends _i50.PageRouteInfo { - const MisteryBoxRoute({List<_i50.PageRouteInfo>? children}) +class MisteryBoxRoute extends _i52.PageRouteInfo { + const MisteryBoxRoute({List<_i52.PageRouteInfo>? children}) : super(MisteryBoxRoute.name, initialChildren: children); static const String name = 'MisteryBoxRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i23.MisteryBoxPage(); @@ -639,13 +643,13 @@ class MisteryBoxRoute extends _i50.PageRouteInfo { /// generated route for /// [_i24.NotificationPage] -class NotificationRoute extends _i50.PageRouteInfo { - const NotificationRoute({List<_i50.PageRouteInfo>? children}) +class NotificationRoute extends _i52.PageRouteInfo { + const NotificationRoute({List<_i52.PageRouteInfo>? children}) : super(NotificationRoute.name, initialChildren: children); static const String name = 'NotificationRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i24.NotificationPage(); @@ -655,13 +659,13 @@ class NotificationRoute extends _i50.PageRouteInfo { /// generated route for /// [_i25.OnboardingPage] -class OnboardingRoute extends _i50.PageRouteInfo { - const OnboardingRoute({List<_i50.PageRouteInfo>? children}) +class OnboardingRoute extends _i52.PageRouteInfo { + const OnboardingRoute({List<_i52.PageRouteInfo>? children}) : super(OnboardingRoute.name, initialChildren: children); static const String name = 'OnboardingRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i25.OnboardingPage(); @@ -671,11 +675,11 @@ class OnboardingRoute extends _i50.PageRouteInfo { /// generated route for /// [_i26.OrderDetailPage] -class OrderDetailRoute extends _i50.PageRouteInfo { +class OrderDetailRoute extends _i52.PageRouteInfo { OrderDetailRoute({ - _i51.Key? key, - required _i54.CustomerOrder order, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + required _i56.CustomerOrder order, + List<_i52.PageRouteInfo>? children, }) : super( OrderDetailRoute.name, args: OrderDetailRouteArgs(key: key, order: order), @@ -684,11 +688,11 @@ class OrderDetailRoute extends _i50.PageRouteInfo { static const String name = 'OrderDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( + return _i52.WrappedRoute( child: _i26.OrderDetailPage(key: args.key, order: args.order), ); }, @@ -698,9 +702,9 @@ class OrderDetailRoute extends _i50.PageRouteInfo { class OrderDetailRouteArgs { const OrderDetailRouteArgs({this.key, required this.order}); - final _i51.Key? key; + final _i53.Key? key; - final _i54.CustomerOrder order; + final _i56.CustomerOrder order; @override String toString() { @@ -720,28 +724,28 @@ class OrderDetailRouteArgs { /// generated route for /// [_i27.OrderPage] -class OrderRoute extends _i50.PageRouteInfo { - const OrderRoute({List<_i50.PageRouteInfo>? children}) +class OrderRoute extends _i52.PageRouteInfo { + const OrderRoute({List<_i52.PageRouteInfo>? children}) : super(OrderRoute.name, initialChildren: children); static const String name = 'OrderRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i27.OrderPage()); + return _i52.WrappedRoute(child: const _i27.OrderPage()); }, ); } /// generated route for /// [_i28.OtpPage] -class OtpRoute extends _i50.PageRouteInfo { +class OtpRoute extends _i52.PageRouteInfo { OtpRoute({ - _i51.Key? key, + _i53.Key? key, required String registrationToken, required String phoneNumber, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( OtpRoute.name, args: OtpRouteArgs( @@ -754,11 +758,11 @@ class OtpRoute extends _i50.PageRouteInfo { static const String name = 'OtpRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( + return _i52.WrappedRoute( child: _i28.OtpPage( key: args.key, registrationToken: args.registrationToken, @@ -776,7 +780,7 @@ class OtpRouteArgs { required this.phoneNumber, }); - final _i51.Key? key; + final _i53.Key? key; final String registrationToken; @@ -803,13 +807,13 @@ class OtpRouteArgs { /// generated route for /// [_i29.OutletPage] -class OutletRoute extends _i50.PageRouteInfo { - const OutletRoute({List<_i50.PageRouteInfo>? children}) +class OutletRoute extends _i52.PageRouteInfo { + const OutletRoute({List<_i52.PageRouteInfo>? children}) : super(OutletRoute.name, initialChildren: children); static const String name = 'OutletRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i29.OutletPage(); @@ -819,11 +823,11 @@ class OutletRoute extends _i50.PageRouteInfo { /// generated route for /// [_i30.PasswordPage] -class PasswordRoute extends _i50.PageRouteInfo { +class PasswordRoute extends _i52.PageRouteInfo { PasswordRoute({ - _i51.Key? key, + _i53.Key? key, required String phoneNumber, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( PasswordRoute.name, args: PasswordRouteArgs(key: key, phoneNumber: phoneNumber), @@ -832,11 +836,11 @@ class PasswordRoute extends _i50.PageRouteInfo { static const String name = 'PasswordRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( + return _i52.WrappedRoute( child: _i30.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), ); }, @@ -846,7 +850,7 @@ class PasswordRoute extends _i50.PageRouteInfo { class PasswordRouteArgs { const PasswordRouteArgs({this.key, required this.phoneNumber}); - final _i51.Key? key; + final _i53.Key? key; final String phoneNumber; @@ -868,29 +872,29 @@ class PasswordRouteArgs { /// generated route for /// [_i31.PaymentCodePage] -class PaymentCodeRoute extends _i50.PageRouteInfo { - const PaymentCodeRoute({List<_i50.PageRouteInfo>? children}) +class PaymentCodeRoute extends _i52.PageRouteInfo { + const PaymentCodeRoute({List<_i52.PageRouteInfo>? children}) : super(PaymentCodeRoute.name, initialChildren: children); static const String name = 'PaymentCodeRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i31.PaymentCodePage()); + return _i52.WrappedRoute(child: const _i31.PaymentCodePage()); }, ); } /// generated route for /// [_i32.PaymentPage] -class PaymentRoute extends _i50.PageRouteInfo { - const PaymentRoute({List<_i50.PageRouteInfo>? children}) +class PaymentRoute extends _i52.PageRouteInfo { + const PaymentRoute({List<_i52.PageRouteInfo>? children}) : super(PaymentRoute.name, initialChildren: children); static const String name = 'PaymentRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i32.PaymentPage(); @@ -900,28 +904,28 @@ class PaymentRoute extends _i50.PageRouteInfo { /// generated route for /// [_i33.PinChangePage] -class PinChangeRoute extends _i50.PageRouteInfo { - const PinChangeRoute({List<_i50.PageRouteInfo>? children}) +class PinChangeRoute extends _i52.PageRouteInfo { + const PinChangeRoute({List<_i52.PageRouteInfo>? children}) : super(PinChangeRoute.name, initialChildren: children); static const String name = 'PinChangeRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i33.PinChangePage()); + return _i52.WrappedRoute(child: const _i33.PinChangePage()); }, ); } /// generated route for /// [_i34.PinPage] -class PinRoute extends _i50.PageRouteInfo { +class PinRoute extends _i52.PageRouteInfo { PinRoute({ - _i51.Key? key, + _i53.Key? key, bool isCreatePin = true, String? title, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( PinRoute.name, args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title), @@ -930,7 +934,7 @@ class PinRoute extends _i50.PageRouteInfo { static const String name = 'PinRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -948,7 +952,7 @@ class PinRoute extends _i50.PageRouteInfo { class PinRouteArgs { const PinRouteArgs({this.key, this.isCreatePin = true, this.title}); - final _i51.Key? key; + final _i53.Key? key; final bool isCreatePin; @@ -974,13 +978,13 @@ class PinRouteArgs { /// generated route for /// [_i35.PinSettingsPage] -class PinSettingsRoute extends _i50.PageRouteInfo { - const PinSettingsRoute({List<_i50.PageRouteInfo>? children}) +class PinSettingsRoute extends _i52.PageRouteInfo { + const PinSettingsRoute({List<_i52.PageRouteInfo>? children}) : super(PinSettingsRoute.name, initialChildren: children); static const String name = 'PinSettingsRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i35.PinSettingsPage(); @@ -990,11 +994,11 @@ class PinSettingsRoute extends _i50.PageRouteInfo { /// generated route for /// [_i36.PinSetupPage] -class PinSetupRoute extends _i50.PageRouteInfo { +class PinSetupRoute extends _i52.PageRouteInfo { PinSetupRoute({ - _i51.Key? key, - required _i54.PinOtpPurpose purpose, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + required _i56.PinOtpPurpose purpose, + List<_i52.PageRouteInfo>? children, }) : super( PinSetupRoute.name, args: PinSetupRouteArgs(key: key, purpose: purpose), @@ -1003,11 +1007,11 @@ class PinSetupRoute extends _i50.PageRouteInfo { static const String name = 'PinSetupRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( + return _i52.WrappedRoute( child: _i36.PinSetupPage(key: args.key, purpose: args.purpose), ); }, @@ -1017,9 +1021,9 @@ class PinSetupRoute extends _i50.PageRouteInfo { class PinSetupRouteArgs { const PinSetupRouteArgs({this.key, required this.purpose}); - final _i51.Key? key; + final _i53.Key? key; - final _i54.PinOtpPurpose purpose; + final _i56.PinOtpPurpose purpose; @override String toString() { @@ -1039,13 +1043,13 @@ class PinSetupRouteArgs { /// generated route for /// [_i37.PointPage] -class PointRoute extends _i50.PageRouteInfo { - const PointRoute({List<_i50.PageRouteInfo>? children}) +class PointRoute extends _i52.PageRouteInfo { + const PointRoute({List<_i52.PageRouteInfo>? children}) : super(PointRoute.name, initialChildren: children); static const String name = 'PointRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { return const _i37.PointPage(); @@ -1055,12 +1059,12 @@ class PointRoute extends _i50.PageRouteInfo { /// generated route for /// [_i38.ProductRedeemPage] -class ProductRedeemRoute extends _i50.PageRouteInfo { +class ProductRedeemRoute extends _i52.PageRouteInfo { ProductRedeemRoute({ - _i51.Key? key, + _i53.Key? key, required _i4.Product product, required _i4.PointCard pointCard, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( ProductRedeemRoute.name, args: ProductRedeemRouteArgs( @@ -1073,7 +1077,7 @@ class ProductRedeemRoute extends _i50.PageRouteInfo { static const String name = 'ProductRedeemRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1093,7 +1097,7 @@ class ProductRedeemRouteArgs { required this.pointCard, }); - final _i51.Key? key; + final _i53.Key? key; final _i4.Product product; @@ -1119,27 +1123,90 @@ class ProductRedeemRouteArgs { /// generated route for /// [_i39.ProfilePage] -class ProfileRoute extends _i50.PageRouteInfo { - const ProfileRoute({List<_i50.PageRouteInfo>? children}) +class ProfileRoute extends _i52.PageRouteInfo { + const ProfileRoute({List<_i52.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i39.ProfilePage()); + return _i52.WrappedRoute(child: const _i39.ProfilePage()); }, ); } /// generated route for -/// [_i40.RegisterPage] -class RegisterRoute extends _i50.PageRouteInfo { +/// [_i40.PromoDetailPage] +class PromoDetailRoute extends _i52.PageRouteInfo { + PromoDetailRoute({ + _i53.Key? key, + required _i57.Promo promo, + List<_i52.PageRouteInfo>? children, + }) : super( + PromoDetailRoute.name, + args: PromoDetailRouteArgs(key: key, promo: promo), + initialChildren: children, + ); + + static const String name = 'PromoDetailRoute'; + + static _i52.PageInfo page = _i52.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i40.PromoDetailPage(key: args.key, promo: args.promo); + }, + ); +} + +class PromoDetailRouteArgs { + const PromoDetailRouteArgs({this.key, required this.promo}); + + final _i53.Key? key; + + final _i57.Promo promo; + + @override + String toString() { + return 'PromoDetailRouteArgs{key: $key, promo: $promo}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! PromoDetailRouteArgs) return false; + return key == other.key && promo == other.promo; + } + + @override + int get hashCode => key.hashCode ^ promo.hashCode; +} + +/// generated route for +/// [_i41.PromoPage] +class PromoRoute extends _i52.PageRouteInfo { + const PromoRoute({List<_i52.PageRouteInfo>? children}) + : super(PromoRoute.name, initialChildren: children); + + static const String name = 'PromoRoute'; + + static _i52.PageInfo page = _i52.PageInfo( + name, + builder: (data) { + return const _i41.PromoPage(); + }, + ); +} + +/// generated route for +/// [_i42.RegisterPage] +class RegisterRoute extends _i52.PageRouteInfo { RegisterRoute({ - _i51.Key? key, + _i53.Key? key, required String phoneNumber, - List<_i50.PageRouteInfo>? children, + List<_i52.PageRouteInfo>? children, }) : super( RegisterRoute.name, args: RegisterRouteArgs(key: key, phoneNumber: phoneNumber), @@ -1148,12 +1215,12 @@ class RegisterRoute extends _i50.PageRouteInfo { static const String name = 'RegisterRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i50.WrappedRoute( - child: _i40.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), + return _i52.WrappedRoute( + child: _i42.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), ); }, ); @@ -1162,7 +1229,7 @@ class RegisterRoute extends _i50.PageRouteInfo { class RegisterRouteArgs { const RegisterRouteArgs({this.key, required this.phoneNumber}); - final _i51.Key? key; + final _i53.Key? key; final String phoneNumber; @@ -1183,108 +1250,108 @@ class RegisterRouteArgs { } /// generated route for -/// [_i41.RewardPage] -class RewardRoute extends _i50.PageRouteInfo { - const RewardRoute({List<_i50.PageRouteInfo>? children}) +/// [_i43.RewardPage] +class RewardRoute extends _i52.PageRouteInfo { + const RewardRoute({List<_i52.PageRouteInfo>? children}) : super(RewardRoute.name, initialChildren: children); static const String name = 'RewardRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return const _i41.RewardPage(); + return const _i43.RewardPage(); }, ); } /// generated route for -/// [_i42.SplashPage] -class SplashRoute extends _i50.PageRouteInfo { - const SplashRoute({List<_i50.PageRouteInfo>? children}) +/// [_i44.SplashPage] +class SplashRoute extends _i52.PageRouteInfo { + const SplashRoute({List<_i52.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return const _i42.SplashPage(); + return const _i44.SplashPage(); }, ); } /// generated route for -/// [_i43.TransferPage] -class TransferRoute extends _i50.PageRouteInfo { - const TransferRoute({List<_i50.PageRouteInfo>? children}) +/// [_i45.TransferPage] +class TransferRoute extends _i52.PageRouteInfo { + const TransferRoute({List<_i52.PageRouteInfo>? children}) : super(TransferRoute.name, initialChildren: children); static const String name = 'TransferRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i43.TransferPage()); + return _i52.WrappedRoute(child: const _i45.TransferPage()); }, ); } /// generated route for -/// [_i44.VoucherDetailPage] -class VoucherDetailRoute extends _i50.PageRouteInfo { - const VoucherDetailRoute({List<_i50.PageRouteInfo>? children}) +/// [_i46.VoucherDetailPage] +class VoucherDetailRoute extends _i52.PageRouteInfo { + const VoucherDetailRoute({List<_i52.PageRouteInfo>? children}) : super(VoucherDetailRoute.name, initialChildren: children); static const String name = 'VoucherDetailRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return const _i44.VoucherDetailPage(); + return const _i46.VoucherDetailPage(); }, ); } /// generated route for -/// [_i45.VoucherPage] -class VoucherRoute extends _i50.PageRouteInfo { - const VoucherRoute({List<_i50.PageRouteInfo>? children}) +/// [_i47.VoucherPage] +class VoucherRoute extends _i52.PageRouteInfo { + const VoucherRoute({List<_i52.PageRouteInfo>? children}) : super(VoucherRoute.name, initialChildren: children); static const String name = 'VoucherRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return const _i45.VoucherPage(); + return const _i47.VoucherPage(); }, ); } /// generated route for -/// [_i46.WalletExpiringPage] -class WalletExpiringRoute extends _i50.PageRouteInfo { - const WalletExpiringRoute({List<_i50.PageRouteInfo>? children}) +/// [_i48.WalletExpiringPage] +class WalletExpiringRoute extends _i52.PageRouteInfo { + const WalletExpiringRoute({List<_i52.PageRouteInfo>? children}) : super(WalletExpiringRoute.name, initialChildren: children); static const String name = 'WalletExpiringRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return _i50.WrappedRoute(child: const _i46.WalletExpiringPage()); + return _i52.WrappedRoute(child: const _i48.WalletExpiringPage()); }, ); } /// generated route for -/// [_i47.WalletHistoryPage] -class WalletHistoryRoute extends _i50.PageRouteInfo { +/// [_i49.WalletHistoryPage] +class WalletHistoryRoute extends _i52.PageRouteInfo { WalletHistoryRoute({ - _i51.Key? key, - _i54.WalletCurrency initialCurrency = _i54.WalletCurrency.point, - List<_i50.PageRouteInfo>? children, + _i53.Key? key, + _i56.WalletCurrency initialCurrency = _i56.WalletCurrency.point, + List<_i52.PageRouteInfo>? children, }) : super( WalletHistoryRoute.name, args: WalletHistoryRouteArgs( @@ -1296,14 +1363,14 @@ class WalletHistoryRoute extends _i50.PageRouteInfo { static const String name = 'WalletHistoryRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const WalletHistoryRouteArgs(), ); - return _i50.WrappedRoute( - child: _i47.WalletHistoryPage( + return _i52.WrappedRoute( + child: _i49.WalletHistoryPage( key: args.key, initialCurrency: args.initialCurrency, ), @@ -1315,12 +1382,12 @@ class WalletHistoryRoute extends _i50.PageRouteInfo { class WalletHistoryRouteArgs { const WalletHistoryRouteArgs({ this.key, - this.initialCurrency = _i54.WalletCurrency.point, + this.initialCurrency = _i56.WalletCurrency.point, }); - final _i51.Key? key; + final _i53.Key? key; - final _i54.WalletCurrency initialCurrency; + final _i56.WalletCurrency initialCurrency; @override String toString() { @@ -1339,31 +1406,31 @@ class WalletHistoryRouteArgs { } /// generated route for -/// [_i48.WalletPage] -class WalletRoute extends _i50.PageRouteInfo { - const WalletRoute({List<_i50.PageRouteInfo>? children}) +/// [_i50.WalletPage] +class WalletRoute extends _i52.PageRouteInfo { + const WalletRoute({List<_i52.PageRouteInfo>? children}) : super(WalletRoute.name, initialChildren: children); static const String name = 'WalletRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { - return const _i48.WalletPage(); + return const _i50.WalletPage(); }, ); } /// generated route for -/// [_i49.WebViewPage] -class WebViewRoute extends _i50.PageRouteInfo { +/// [_i51.WebViewPage] +class WebViewRoute extends _i52.PageRouteInfo { WebViewRoute({ - _i51.Key? key, + _i53.Key? key, required String title, required String url, bool fullscreen = false, - _i51.Color? backgroundColor, - List<_i50.PageRouteInfo>? children, + _i53.Color? backgroundColor, + List<_i52.PageRouteInfo>? children, }) : super( WebViewRoute.name, args: WebViewRouteArgs( @@ -1378,11 +1445,11 @@ class WebViewRoute extends _i50.PageRouteInfo { static const String name = 'WebViewRoute'; - static _i50.PageInfo page = _i50.PageInfo( + static _i52.PageInfo page = _i52.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i49.WebViewPage( + return _i51.WebViewPage( key: args.key, title: args.title, url: args.url, @@ -1402,7 +1469,7 @@ class WebViewRouteArgs { this.backgroundColor, }); - final _i51.Key? key; + final _i53.Key? key; final String title; @@ -1410,7 +1477,7 @@ class WebViewRouteArgs { final bool fullscreen; - final _i51.Color? backgroundColor; + final _i53.Color? backgroundColor; @override String toString() { diff --git a/lib/sample/promo_sample_data.dart b/lib/sample/promo_sample_data.dart new file mode 100644 index 0000000..3840f5c --- /dev/null +++ b/lib/sample/promo_sample_data.dart @@ -0,0 +1,209 @@ +// Models +// +// Mengikuti bentuk campaign di backend: tipe hadiah (POINTS / COINS / REWARD) +// dengan satu aturan (min. belanja, menu, hari, atau outlet) dan periode. + +/// Hadiah yang didapat customer dari promo. +enum PromoType { point, coin, reward } + +class Promo { + final String id; + final String title; + + /// Satu kalimat pendek untuk kartu. + final String subtitle; + final PromoType type; + + /// Hadiah dalam bentuk ringkas, mis. "2x EnakPoint" atau "+3 EnakCoin". + final String rewardLabel; + final String description; + final List steps; + final List terms; + + /// Kosong berarti berlaku di semua outlet. + final List outlets; + final DateTime startDate; + final DateTime endDate; + final bool isFeatured; + + const Promo({ + required this.id, + required this.title, + required this.subtitle, + required this.type, + required this.rewardLabel, + required this.description, + required this.steps, + required this.terms, + required this.startDate, + required this.endDate, + this.outlets = const [], + this.isFeatured = false, + }); +} + +// Sample Data +// +// 1 EnakPoint = potongan Rp 1. Belanja biasa memberi sekitar 1% EnakPoint. +DateTime _daysAgo(int days) => DateTime.now().subtract(Duration(days: days)); +DateTime _daysFromNow(int days) => DateTime.now().add(Duration(days: days)); + +const String _stepLinkPhone = + 'Sebutkan nomor HP akunmu ke kasir sebelum membayar.'; + +/// Getter, bukan `final`, supaya perubahan data langsung terbaca saat hot +/// reload (variabel global yang sudah terisi tidak diisi ulang). +List get promoSamples => [ + Promo( + id: 'p1', + title: 'Double EnakPoint Akhir Pekan', + subtitle: 'Semua belanja Sabtu & Minggu dapat EnakPoint 2x lipat', + type: PromoType.point, + rewardLabel: '2x EnakPoint', + description: + 'Belanja di akhir pekan jadi lebih untung. Setiap transaksi pada hari ' + 'Sabtu dan Minggu mendapat EnakPoint dua kali lipat dari biasanya.', + steps: [ + 'Belanja di outlet mana pun pada hari Sabtu atau Minggu.', + _stepLinkPhone, + 'EnakPoint 2x otomatis masuk setelah pesanan lunas.', + ], + terms: [ + 'Berlaku untuk semua menu, kecuali pembelian EnakCoin.', + 'Bonus dihitung dari total belanja setelah diskon.', + 'Maksimal bonus 5.000 EnakPoint per transaksi.', + ], + startDate: _daysAgo(6), + endDate: _daysFromNow(24), + isFeatured: true, + ), + Promo( + id: 'p2', + title: 'Ngopi Dapat EnakCoin', + subtitle: 'Setiap Kopi Susu di Tumulu Coffee dapat +3 EnakCoin', + type: PromoType.coin, + rewardLabel: '+3 EnakCoin', + description: + 'Kopi pagi sekaligus modal main EnakGame. Setiap pembelian Kopi Susu ' + 'di Tumulu Coffee memberi 3 EnakCoin tambahan.', + steps: [ + 'Pesan Kopi Susu ukuran apa pun di Tumulu Coffee.', + _stepLinkPhone, + 'EnakCoin masuk otomatis setelah pesanan lunas.', + ], + terms: [ + 'Berlaku kelipatan: 2 Kopi Susu = +6 EnakCoin.', + 'Maksimal 15 EnakCoin per hari.', + ], + outlets: ['Tumulu Coffee'], + startDate: _daysAgo(4), + endDate: _daysFromNow(10), + isFeatured: true, + ), + Promo( + id: 'p3', + title: 'Belanja Rp 100 Ribu, Bonus 2.000 EnakPoint', + subtitle: 'Bonus langsung untuk belanja minimal Rp 100.000', + type: PromoType.point, + rewardLabel: '+2.000 EnakPoint', + description: + 'Dapatkan bonus 2.000 EnakPoint, setara potongan Rp 2.000 di belanja ' + 'berikutnya, untuk setiap transaksi minimal Rp 100.000.', + steps: [ + 'Belanja minimal Rp 100.000 dalam satu transaksi.', + _stepLinkPhone, + 'Bonus masuk otomatis, di luar EnakPoint belanja biasa.', + ], + terms: [ + 'Minimum belanja dihitung sebelum pajak.', + 'Berlaku 1 kali per customer per hari.', + ], + startDate: _daysAgo(12), + endDate: _daysFromNow(2), + ), + Promo( + id: 'p4', + title: 'Gratis Es Teh Manis', + subtitle: 'Belanja Rp 50.000 di Bakso 343, dapat reward Es Teh Manis', + type: PromoType.reward, + rewardLabel: 'Gratis Es Teh', + description: + 'Makan bakso makin segar. Belanja minimal Rp 50.000 di Bakso 343 dan ' + 'dapatkan reward 1 Es Teh Manis untuk kunjungan berikutnya.', + steps: [ + 'Belanja minimal Rp 50.000 di Bakso 343.', + _stepLinkPhone, + 'Reward masuk ke Reward Saya dan berlaku 14 hari.', + ], + terms: [ + 'Reward hanya bisa dipakai di Bakso 343.', + 'Maksimal 1 reward per customer per hari.', + ], + outlets: ['Bakso 343'], + startDate: _daysAgo(2), + endDate: _daysFromNow(14), + isFeatured: true, + ), + Promo( + id: 'p5', + title: 'Rabu Ramen', + subtitle: 'Setiap Rabu, tiap porsi ramen dapat +1.500 EnakPoint', + type: PromoType.point, + rewardLabel: '+1.500 EnakPoint', + description: + 'Hari Rabu jadi hari ramen. Setiap porsi ramen di Ramenmulu pada hari ' + 'Rabu memberi bonus 1.500 EnakPoint.', + steps: [ + 'Pesan ramen di Ramenmulu pada hari Rabu.', + _stepLinkPhone, + 'Bonus masuk otomatis setelah pesanan lunas.', + ], + terms: [ + 'Berlaku untuk semua varian ramen, tidak termasuk topping.', + 'Maksimal 3 porsi per transaksi.', + ], + outlets: ['Ramenmulu'], + startDate: _daysAgo(20), + endDate: _daysFromNow(21), + ), + Promo( + id: 'p6', + title: 'Modal Main EnakGame', + subtitle: 'Belanja Rp 75.000 dapat +5 EnakCoin', + type: PromoType.coin, + rewardLabel: '+5 EnakCoin', + description: + 'Tambah kesempatan menang hadiah di EnakGame. Setiap belanja minimal ' + 'Rp 75.000 memberi 5 EnakCoin tambahan.', + steps: [ + 'Belanja minimal Rp 75.000 di outlet mana pun.', + _stepLinkPhone, + 'Pakai EnakCoin untuk main EnakGame.', + ], + terms: [ + 'Berlaku 1 kali per customer per hari.', + 'EnakCoin mengikuti masa berlaku yang diatur outlet.', + ], + startDate: _daysAgo(1), + endDate: _daysFromNow(7), + ), + Promo( + id: 'p7', + title: 'Pekan Woku Pedas', + subtitle: 'Menu apa pun di Woku Pedas dapat EnakPoint 3x lipat', + type: PromoType.point, + rewardLabel: '3x EnakPoint', + description: + 'Rayakan pembukaan menu baru Woku Pedas. Selama sepekan, semua ' + 'belanja di Woku Pedas mendapat EnakPoint tiga kali lipat.', + steps: [ + 'Belanja di Woku Pedas selama periode promo.', + _stepLinkPhone, + 'EnakPoint 3x otomatis masuk setelah pesanan lunas.', + ], + terms: ['Maksimal bonus 10.000 EnakPoint per transaksi.'], + outlets: ['Woku Pedas'], + startDate: _daysFromNow(3), + endDate: _daysFromNow(10), + ), +];