From e2737852686e23820ad72440f58a78ec5cfd35f9 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 1 Oct 2026 02:47:47 +0700 Subject: [PATCH] EnakShop: katalog toko online - Beranda toko: pencarian, banner promo, grid kategori, produk promo, dan rekomendasi - Halaman kategori dengan urutan terlaris/terbaru/termurah/termahal - Detail produk: harga diskon, varian, info, deskripsi, produk serupa - Data contoh 14 produk dan 3 banner; keranjang menyusul - DetailSection dan NumberedStep jadi komponen bersama - Tinggi banner EnakPromo dan EnakShop ikut skala font sistem Co-Authored-By: Claude Opus 5.5 --- .../components/card/detail_section.dart | 74 ++ .../pages/home/widgets/service_section.dart | 5 +- .../pages/promo_detail/promo_detail_page.dart | 80 +-- lib/presentation/pages/promo/promo_page.dart | 3 +- .../shop_category/shop_category_page.dart | 135 ++++ .../shop_product_detail_page.dart | 265 +++++++ lib/presentation/pages/shop/shop_page.dart | 273 ++++++++ lib/presentation/pages/shop/shop_ui.dart | 139 ++++ .../shop/widgets/shop_category_grid.dart | 66 ++ .../pages/shop/widgets/shop_product_card.dart | 79 +++ .../pages/shop/widgets/shop_promo_banner.dart | 107 +++ lib/presentation/router/app_router.dart | 5 + lib/presentation/router/app_router.gr.dart | 646 +++++++++++------- lib/sample/shop_sample_data.dart | 368 ++++++++++ 14 files changed, 1911 insertions(+), 334 deletions(-) create mode 100644 lib/presentation/components/card/detail_section.dart create mode 100644 lib/presentation/pages/shop/pages/shop_category/shop_category_page.dart create mode 100644 lib/presentation/pages/shop/pages/shop_product_detail/shop_product_detail_page.dart create mode 100644 lib/presentation/pages/shop/shop_page.dart create mode 100644 lib/presentation/pages/shop/shop_ui.dart create mode 100644 lib/presentation/pages/shop/widgets/shop_category_grid.dart create mode 100644 lib/presentation/pages/shop/widgets/shop_product_card.dart create mode 100644 lib/presentation/pages/shop/widgets/shop_promo_banner.dart create mode 100644 lib/sample/shop_sample_data.dart diff --git a/lib/presentation/components/card/detail_section.dart b/lib/presentation/components/card/detail_section.dart new file mode 100644 index 0000000..b59958f --- /dev/null +++ b/lib/presentation/components/card/detail_section.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +/// Kotak putih berjudul untuk mengelompokkan isi halaman detail. +class DetailSection extends StatelessWidget { + const DetailSection({super.key, 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, + ], + ), + ); + } +} + +/// Satu langkah bernomor, mis. untuk "Cara dapat" atau "Cara beli". +class NumberedStep extends StatelessWidget { + const NumberedStep({super.key, 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))), + ], + ), + ); + } +} 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 5740b8a..77e1e36 100644 --- a/lib/presentation/pages/main/pages/home/widgets/service_section.dart +++ b/lib/presentation/pages/main/pages/home/widgets/service_section.dart @@ -62,10 +62,7 @@ class HomeServiceSection extends StatelessWidget { _HomeMenu( label: 'EnakShop', icon: Assets.icons.enakshopIcon, - route: ComingSoonRoute( - title: 'EnakShop', - iconPath: Assets.icons.enakshopIcon.path, - ), + route: const ShopRoute(), ), _HomeMenu( label: 'EnakNews', 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 index 9705674..52c8f2e 100644 --- a/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart +++ b/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import '../../../../../common/theme/theme.dart'; import '../../../../../sample/promo_sample_data.dart'; import '../../../../components/button/button.dart'; +import '../../../../components/card/detail_section.dart'; import '../../../../router/app_router.gr.dart'; import '../../promo_ui.dart'; @@ -29,7 +30,7 @@ class PromoDetailPage extends StatelessWidget { _buildUpcomingNote(), ], const SizedBox(height: 12), - _Section( + DetailSection( child: Column( children: [ _InfoRow( @@ -54,7 +55,7 @@ class PromoDetailPage extends StatelessWidget { ), ), const SizedBox(height: 12), - _Section( + DetailSection( title: 'Tentang promo', child: Text( promo.description, @@ -65,18 +66,18 @@ class PromoDetailPage extends StatelessWidget { ), ), const SizedBox(height: 12), - _Section( + DetailSection( title: 'Cara dapat', child: Column( children: [ for (var i = 0; i < promo.steps.length; i++) - _StepRow(number: i + 1, text: promo.steps[i]), + NumberedStep(number: i + 1, text: promo.steps[i]), ], ), ), if (promo.terms.isNotEmpty) ...[ const SizedBox(height: 12), - _Section( + DetailSection( title: 'Syarat & Ketentuan', child: Column( children: [ @@ -201,38 +202,6 @@ class PromoDetailPage extends StatelessWidget { } } -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, @@ -282,43 +251,6 @@ class _InfoRow extends StatelessWidget { } } -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}); diff --git a/lib/presentation/pages/promo/promo_page.dart b/lib/presentation/pages/promo/promo_page.dart index 46b636a..ecd55dc 100644 --- a/lib/presentation/pages/promo/promo_page.dart +++ b/lib/presentation/pages/promo/promo_page.dart @@ -104,7 +104,8 @@ class _PromoPageState extends State { children: [ const SizedBox(height: 16), SizedBox( - height: 168, + // Ikut skala font sistem supaya teks banner tidak terpotong. + height: MediaQuery.textScalerOf(context).scale(168), child: PageView.builder( controller: _bannerController, itemCount: featured.length, diff --git a/lib/presentation/pages/shop/pages/shop_category/shop_category_page.dart b/lib/presentation/pages/shop/pages/shop_category/shop_category_page.dart new file mode 100644 index 0000000..7162bef --- /dev/null +++ b/lib/presentation/pages/shop/pages/shop_category/shop_category_page.dart @@ -0,0 +1,135 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import '../../../../../sample/shop_sample_data.dart'; +import '../../../../components/delegate/filter_bar_delegate.dart'; +import '../../../../router/app_router.gr.dart'; +import '../../shop_ui.dart'; +import '../../widgets/shop_product_card.dart'; + +enum _ShopSort { + popular('Terlaris'), + newest('Terbaru'), + cheapest('Termurah'), + priciest('Termahal'); + + const _ShopSort(this.label); + + final String label; + + int compare(ShopProduct a, ShopProduct b) => switch (this) { + _ShopSort.popular => b.soldCount.compareTo(a.soldCount), + _ShopSort.newest => b.addedAt.compareTo(a.addedAt), + _ShopSort.cheapest => a.finalPrice.compareTo(b.finalPrice), + _ShopSort.priciest => b.finalPrice.compareTo(a.finalPrice), + }; +} + +/// Daftar produk satu kategori, semua produk (`category` kosong), atau hanya +/// yang sedang diskon ([promoOnly]). +@RoutePage() +class ShopCategoryPage extends StatefulWidget { + const ShopCategoryPage({super.key, this.category, this.promoOnly = false}); + + final ShopCategory? category; + final bool promoOnly; + + @override + State createState() => _ShopCategoryPageState(); +} + +class _ShopCategoryPageState extends State { + _ShopSort _sort = _ShopSort.popular; + + late final List _products = shopSamples + .where( + (p) => + (widget.category == null || p.category == widget.category) && + (!widget.promoOnly || p.isDiscounted), + ) + .toList(); + + String get _title { + if (widget.promoOnly) return 'Lagi Promo'; + return widget.category?.label ?? 'Semua Produk'; + } + + @override + Widget build(BuildContext context) { + final sorted = [..._products]..sort(_sort.compare); + + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: Text(_title)), + body: CustomScrollView( + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: FilterBarDelegate<_ShopSort>( + values: _ShopSort.values, + selected: _sort, + labelOf: (sort) => sort.label, + onSelected: (sort) => setState(() => _sort = sort), + ), + ), + if (sorted.isEmpty) + SliverFillRemaining(hasScrollBody: false, child: _buildEmpty()) + else ...[ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 10), + child: Text( + '${sorted.length} produk', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), + sliver: SliverGrid.builder( + gridDelegate: shopProductGridDelegate(context), + itemCount: sorted.length, + itemBuilder: (context, index) { + final product = sorted[index]; + return ShopProductCard( + product: product, + onTap: () => context.router.push( + ShopProductDetailRoute(product: product), + ), + ); + }, + ), + ), + ], + ], + ), + ); + } + + Widget _buildEmpty() { + return Padding( + padding: const EdgeInsets.fromLTRB(32, 32, 32, 64), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.category?.emoji ?? '๐Ÿ›๏ธ', + style: const TextStyle(fontSize: 44), + ), + const SizedBox(height: 16), + Text( + 'Belum ada produk', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 6), + Text( + 'Produk baru akan muncul di sini.', + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/shop/pages/shop_product_detail/shop_product_detail_page.dart b/lib/presentation/pages/shop/pages/shop_product_detail/shop_product_detail_page.dart new file mode 100644 index 0000000..67a7b5b --- /dev/null +++ b/lib/presentation/pages/shop/pages/shop_product_detail/shop_product_detail_page.dart @@ -0,0 +1,265 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import '../../../../../sample/shop_sample_data.dart'; +import '../../../../components/card/detail_section.dart'; +import '../../../../router/app_router.gr.dart'; +import '../../shop_ui.dart'; +import '../../widgets/shop_product_card.dart'; + +@RoutePage() +class ShopProductDetailPage extends StatefulWidget { + const ShopProductDetailPage({super.key, required this.product}); + + final ShopProduct product; + + @override + State createState() => _ShopProductDetailPageState(); +} + +class _ShopProductDetailPageState extends State { + static const double _relatedCardWidth = 150; + static const int _relatedCount = 6; + static const int _lowStock = 5; + + late String? _variant = widget.product.variants.firstOrNull; + + late final List _related = _findRelated(); + + /// Kategori yang sama lebih dulu, lalu yang terlaris. + List _findRelated() { + final current = widget.product; + final others = shopSamples.where((p) => p.id != current.id).toList() + ..sort((a, b) { + final aSame = a.category == current.category; + final bSame = b.category == current.category; + if (aSame != bSame) return aSame ? -1 : 1; + return b.soldCount.compareTo(a.soldCount); + }); + return others.take(_relatedCount).toList(); + } + + @override + Widget build(BuildContext context) { + final product = widget.product; + + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: const Text('Detail Produk')), + body: ListView( + padding: const EdgeInsets.only(bottom: 24), + children: [ + AspectRatio( + aspectRatio: 1.2, + child: Stack( + fit: StackFit.expand, + children: [ + ShopProductImage(product: product, emojiSize: 120), + if (product.tag != null) + Positioned( + top: 16, + left: 16, + child: ShopTagBadge(tag: product.tag!), + ), + ], + ), + ), + _buildSummary(product), + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), + child: Column( + children: [ + if (product.variants.isNotEmpty) ...[ + DetailSection( + title: '${product.variantLabel}: $_variant', + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final variant in product.variants) + _VariantOption( + label: variant, + isSelected: variant == _variant, + onTap: () => setState(() => _variant = variant), + ), + ], + ), + ), + const SizedBox(height: 12), + ], + DetailSection( + title: 'Info produk', + child: Column( + children: [ + _InfoRow( + label: 'Kategori', + value: product.category.label, + ), + _InfoRow(label: 'Berat / isi', value: product.unit), + ], + ), + ), + const SizedBox(height: 12), + DetailSection( + title: 'Deskripsi', + child: Text( + product.description, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + ), + ], + ), + ), + if (_related.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 20, 16, 10), + child: Text( + 'Produk serupa', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + ), + ), + SizedBox( + height: _relatedCardWidth + shopCardInfoHeight(context), + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: _related.length, + separatorBuilder: (_, _) => const SizedBox(width: 12), + itemBuilder: (context, index) { + final related = _related[index]; + return SizedBox( + width: _relatedCardWidth, + child: ShopProductCard( + product: related, + onTap: () => context.router.push( + ShopProductDetailRoute(product: related), + ), + ), + ); + }, + ), + ), + ], + ], + ), + ); + } + + Widget _buildSummary(ShopProduct product) { + final isLowStock = product.stock <= _lowStock; + + return Container( + width: double.infinity, + color: AppColor.white, + padding: const EdgeInsets.fromLTRB(16, 16, 16, 18), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShopPrice(product: product, large: true), + const SizedBox(height: 10), + Text( + product.name, + style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 6), + Text.rich( + TextSpan( + children: [ + TextSpan(text: formatSoldCount(product.soldCount)), + const TextSpan(text: ' ยท '), + TextSpan( + text: isLowStock + ? 'Sisa ${product.stock}' + : 'Stok ${product.stock}', + style: isLowStock + ? const TextStyle( + color: AppColor.primary, + fontWeight: FontWeight.w700, + ) + : null, + ), + ], + ), + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ); + } +} + +class _VariantOption extends StatelessWidget { + const _VariantOption({ + required this.label, + required this.isSelected, + required this.onTap, + }); + + final String label; + final bool isSelected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Material( + color: isSelected ? AppColor.primary.withOpacity(0.06) : AppColor.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isSelected ? AppColor.primary : AppColor.border, + width: isSelected ? 1.5 : 1, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + child: Text( + label, + style: AppStyle.sm.copyWith( + color: isSelected ? AppColor.primary : AppColor.textPrimary, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500, + ), + ), + ), + ), + ); + } +} + +class _InfoRow extends StatelessWidget { + const _InfoRow({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 110, + child: Text( + label, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ), + Expanded( + child: Text( + value, + style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/shop/shop_page.dart b/lib/presentation/pages/shop/shop_page.dart new file mode 100644 index 0000000..4283d22 --- /dev/null +++ b/lib/presentation/pages/shop/shop_page.dart @@ -0,0 +1,273 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import '../../../sample/shop_sample_data.dart'; +import '../../components/field/field.dart'; +import '../../router/app_router.gr.dart'; +import 'shop_ui.dart'; +import 'widgets/shop_category_grid.dart'; +import 'widgets/shop_product_card.dart'; +import 'widgets/shop_promo_banner.dart'; + +@RoutePage() +class ShopPage extends StatefulWidget { + const ShopPage({super.key}); + + @override + State createState() => _ShopPageState(); +} + +class _ShopPageState extends State { + static const double _rowCardWidth = 150; + + final _searchController = TextEditingController(); + final _bannerController = PageController(viewportFraction: 0.9); + int _bannerIndex = 0; + + final List _promos = shopPromoSamples; + + /// Terlaris di atas. + final List _products = shopSamples + ..sort((a, b) => b.soldCount.compareTo(a.soldCount)); + + @override + void initState() { + super.initState(); + _searchController.addListener(() => setState(() {})); + } + + @override + void dispose() { + _searchController.dispose(); + _bannerController.dispose(); + super.dispose(); + } + + void _openProduct(ShopProduct product) => + context.router.push(ShopProductDetailRoute(product: product)); + + void _openCategory({ShopCategory? category, bool promoOnly = false}) => + context.router.push( + ShopCategoryRoute(category: category, promoOnly: promoOnly), + ); + + @override + Widget build(BuildContext context) { + final query = _searchController.text.trim().toLowerCase(); + + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: const Text('EnakShop')), + body: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 12), + child: SearchTextField( + hintText: 'Cari di EnakShop', + prefixIcon: Icons.search, + controller: _searchController, + ), + ), + ), + if (query.isNotEmpty) + ..._buildSearchResults(query) + else ...[ + SliverToBoxAdapter(child: _buildBanners()), + const SliverToBoxAdapter(child: _SectionTitle(title: 'Kategori')), + SliverToBoxAdapter( + child: ShopCategoryGrid( + onTap: (category) => _openCategory(category: category), + ), + ), + SliverToBoxAdapter( + child: _SectionTitle( + title: 'Lagi Promo', + onSeeAll: () => _openCategory(promoOnly: true), + ), + ), + SliverToBoxAdapter( + child: _buildProductRow( + _products.where((p) => p.isDiscounted).toList(), + ), + ), + const SliverToBoxAdapter( + child: _SectionTitle(title: 'Rekomendasi'), + ), + _buildGrid(_products), + ], + const SliverToBoxAdapter(child: SizedBox(height: 24)), + ], + ), + ); + } + + List _buildSearchResults(String query) { + final results = _products + .where( + (p) => + p.name.toLowerCase().contains(query) || + p.category.label.toLowerCase().contains(query), + ) + .toList(); + + if (results.isEmpty) { + return [ + SliverFillRemaining( + hasScrollBody: false, + child: 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( + 'Produk tidak ditemukan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 6), + Text( + 'Coba kata kunci lain, mis. "sambal" atau "kopi".', + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ), + ]; + } + + return [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), + child: Text( + '${results.length} produk ditemukan', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ), + ), + _buildGrid(results), + ]; + } + + Widget _buildBanners() { + return Column( + children: [ + SizedBox( + // Ikut skala font sistem supaya teks banner tidak terpotong. + height: MediaQuery.textScalerOf(context).scale(150), + child: PageView.builder( + controller: _bannerController, + itemCount: _promos.length, + onPageChanged: (index) => setState(() => _bannerIndex = index), + itemBuilder: (context, index) { + final promo = _promos[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: ShopPromoBanner( + promo: promo, + onTap: () => _openCategory(category: promo.category), + ), + ); + }, + ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(_promos.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 _buildProductRow(List products) { + return SizedBox( + height: _rowCardWidth + shopCardInfoHeight(context), + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: products.length, + separatorBuilder: (_, _) => const SizedBox(width: 12), + itemBuilder: (context, index) { + final product = products[index]; + return SizedBox( + width: _rowCardWidth, + child: ShopProductCard( + product: product, + onTap: () => _openProduct(product), + ), + ); + }, + ), + ); + } + + Widget _buildGrid(List products) { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: SliverGrid.builder( + gridDelegate: shopProductGridDelegate(context), + itemCount: products.length, + itemBuilder: (context, index) { + final product = products[index]; + return ShopProductCard( + product: product, + onTap: () => _openProduct(product), + ); + }, + ), + ); + } +} + +class _SectionTitle extends StatelessWidget { + const _SectionTitle({required this.title, this.onSeeAll}); + + final String title; + final VoidCallback? onSeeAll; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(16, 20, onSeeAll == null ? 16 : 4, 10), + child: Row( + children: [ + Expanded( + child: Text( + title, + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + ), + ), + if (onSeeAll != null) + TextButton( + onPressed: onSeeAll, + child: Text( + 'Lihat semua', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/shop/shop_ui.dart b/lib/presentation/pages/shop/shop_ui.dart new file mode 100644 index 0000000..28d0503 --- /dev/null +++ b/lib/presentation/pages/shop/shop_ui.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; + +import '../../../common/extension/extension.dart'; +import '../../../common/theme/theme.dart'; +import '../../../sample/shop_sample_data.dart'; + +/// "320 terjual" / "1,2rb terjual". +String formatSoldCount(int count) { + if (count < 1000) return '$count terjual'; + final thousands = (count / 100).floor() / 10; + final text = thousands == thousands.roundToDouble() + ? thousands.toInt().toString() + : thousands.toString().replaceAll('.', ','); + return '${text}rb terjual'; +} + +/// Grid 2 kolom produk; tinggi kartu = lebar gambar + bagian teks. +SliverGridDelegate shopProductGridDelegate(BuildContext context) { + final itemWidth = (MediaQuery.sizeOf(context).width - 32 - 12) / 2; + return SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + mainAxisExtent: itemWidth + shopCardInfoHeight(context), + ); +} + +/// Tinggi bagian teks kartu produk, ikut skala font sistem supaya tidak +/// terpotong. +double shopCardInfoHeight(BuildContext context) => + MediaQuery.textScalerOf(context).scale(118); + +/// Gambar produk: emoji di atas latar warna produk. +class ShopProductImage extends StatelessWidget { + const ShopProductImage({ + super.key, + required this.product, + this.emojiSize = 56, + }); + + final ShopProduct product; + final double emojiSize; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: Color(product.tint), + child: Center( + child: Text(product.emoji, style: TextStyle(fontSize: emojiSize)), + ), + ); + } +} + +/// Label pojok kartu seperti "Terlaris" atau "Baru". +class ShopTagBadge extends StatelessWidget { + const ShopTagBadge({super.key, required this.tag}); + + final ShopTag tag; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: switch (tag) { + ShopTag.bestSeller => AppColor.primary, + ShopTag.newArrival => AppColor.info, + }, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + tag.label, + style: AppStyle.xs.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w800, + ), + ), + ); + } +} + +/// Harga akhir, dengan harga coret dan persen potongan bila diskon. +class ShopPrice extends StatelessWidget { + const ShopPrice({super.key, required this.product, this.large = false}); + + final ShopProduct product; + final bool large; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + product.finalPrice.currencyFormatRp, + style: (large ? AppStyle.h5 : AppStyle.md).copyWith( + fontWeight: FontWeight.w800, + ), + ), + if (product.isDiscounted) ...[ + SizedBox(height: large ? 4 : 2), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${product.discountPercent}%', + style: (large ? AppStyle.sm : AppStyle.xs).copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w800, + ), + ), + ), + const SizedBox(width: 6), + Flexible( + child: Text( + product.price.currencyFormatRp, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: (large ? AppStyle.sm : AppStyle.xs).copyWith( + color: AppColor.textSecondary, + decoration: TextDecoration.lineThrough, + ), + ), + ), + ], + ), + ], + ], + ); + } +} diff --git a/lib/presentation/pages/shop/widgets/shop_category_grid.dart b/lib/presentation/pages/shop/widgets/shop_category_grid.dart new file mode 100644 index 0000000..7db7e0a --- /dev/null +++ b/lib/presentation/pages/shop/widgets/shop_category_grid.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/shop_sample_data.dart'; + +/// Grid kategori toko, 4 kolom, diakhiri tile "Semua" (`null`). +class ShopCategoryGrid extends StatelessWidget { + const ShopCategoryGrid({super.key, required this.onTap}); + + final ValueChanged onTap; + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 12), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + mainAxisSpacing: 8, + crossAxisSpacing: 4, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(92), + ), + itemCount: ShopCategory.values.length + 1, + itemBuilder: (context, index) { + final category = index < ShopCategory.values.length + ? ShopCategory.values[index] + : null; + return InkWell( + borderRadius: BorderRadius.circular(AppValue.borderRadius), + onTap: () => onTap(category), + child: Column( + children: [ + const SizedBox(height: 4), + Container( + width: 52, + height: 52, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.borderLight), + ), + child: Text( + category?.emoji ?? '๐Ÿช', + style: const TextStyle(fontSize: 26), + ), + ), + const SizedBox(height: 6), + Text( + category?.label ?? 'Semua', + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: AppStyle.xs.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/pages/shop/widgets/shop_product_card.dart b/lib/presentation/pages/shop/widgets/shop_product_card.dart new file mode 100644 index 0000000..1478997 --- /dev/null +++ b/lib/presentation/pages/shop/widgets/shop_product_card.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/shop_sample_data.dart'; +import '../shop_ui.dart'; + +/// Kartu produk untuk grid dan daftar geser. Tinggi bagian teks mengikuti +/// [shopCardInfoHeight]. +class ShopProductCard extends StatelessWidget { + const ShopProductCard({ + super.key, + required this.product, + required this.onTap, + }); + + final ShopProduct product; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Material( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1, + child: Stack( + fit: StackFit.expand, + children: [ + ShopProductImage(product: product), + if (product.tag != null) + Positioned( + top: 10, + left: 10, + child: ShopTagBadge(tag: product.tag!), + ), + ], + ), + ), + SizedBox( + height: shopCardInfoHeight(context), + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + height: 1.3, + ), + ), + const Spacer(), + ShopPrice(product: product), + const SizedBox(height: 4), + Text( + formatSoldCount(product.soldCount), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/shop/widgets/shop_promo_banner.dart b/lib/presentation/pages/shop/widgets/shop_promo_banner.dart new file mode 100644 index 0000000..54157a6 --- /dev/null +++ b/lib/presentation/pages/shop/widgets/shop_promo_banner.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/shop_sample_data.dart'; + +/// Banner promo toko; mengarah ke kategori promo. +class ShopPromoBanner extends StatelessWidget { + const ShopPromoBanner({super.key, required this.promo, required this.onTap}); + + final ShopPromo promo; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final colors = promo.gradient.map(Color.new).toList(); + + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + gradient: LinearGradient( + colors: colors, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: BorderRadius.circular(18), + onTap: onTap, + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: Stack( + children: [ + Positioned( + right: -20, + bottom: -24, + child: Container( + width: 130, + height: 130, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + right: 18, + bottom: 14, + child: Text( + promo.category.emoji, + style: const TextStyle(fontSize: 64), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(18, 16, 110, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + 'Lihat produk', + style: AppStyle.xs.copyWith( + color: colors.last, + fontWeight: FontWeight.w800, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 0727037..808da99 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -91,6 +91,11 @@ class AppRouter extends RootStackRouter { AutoRoute(page: NewsRoute.page), AutoRoute(page: NewsDetailRoute.page), + // Shop + AutoRoute(page: ShopRoute.page), + AutoRoute(page: ShopCategoryRoute.page), + AutoRoute(page: ShopProductDetailRoute.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 e641ecf..0fd7d77 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 _i54; -import 'package:enaklo/domain/wallet/wallet.dart' as _i59; +import 'package:auto_route/auto_route.dart' as _i57; +import 'package:enaklo/domain/wallet/wallet.dart' as _i62; import 'package:enaklo/presentation/pages/account/account_my/account_my_page.dart' as _i1; import 'package:enaklo/presentation/pages/account/address/address_page.dart' @@ -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 _i41; import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' - as _i49; + as _i52; 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; @@ -84,34 +84,40 @@ import 'package:enaklo/presentation/pages/promo/pages/promo_detail/promo_detail_ as _i42; import 'package:enaklo/presentation/pages/promo/promo_page.dart' as _i43; import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i45; -import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i46; -import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' +import 'package:enaklo/presentation/pages/shop/pages/shop_category/shop_category_page.dart' + as _i46; +import 'package:enaklo/presentation/pages/shop/pages/shop_product_detail/shop_product_detail_page.dart' as _i48; +import 'package:enaklo/presentation/pages/shop/shop_page.dart' as _i47; +import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i49; +import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' + as _i51; import 'package:enaklo/presentation/pages/wallet/exchange_page.dart' as _i16; import 'package:enaklo/presentation/pages/wallet/payment_code_page.dart' as _i33; -import 'package:enaklo/presentation/pages/wallet/transfer_page.dart' as _i47; +import 'package:enaklo/presentation/pages/wallet/transfer_page.dart' as _i50; import 'package:enaklo/presentation/pages/wallet/wallet_expiring_page.dart' - as _i50; + as _i53; import 'package:enaklo/presentation/pages/wallet/wallet_history_page.dart' - as _i51; -import 'package:enaklo/presentation/pages/wallet/wallet_page.dart' as _i52; -import 'package:enaklo/presentation/pages/webview/webview_page.dart' as _i53; -import 'package:enaklo/sample/community_sample_data.dart' as _i56; -import 'package:enaklo/sample/news_sample_data.dart' as _i58; -import 'package:enaklo/sample/promo_sample_data.dart' as _i60; -import 'package:enaklo/sample/sample_data.dart' as _i57; -import 'package:flutter/material.dart' as _i55; + as _i54; +import 'package:enaklo/presentation/pages/wallet/wallet_page.dart' as _i55; +import 'package:enaklo/presentation/pages/webview/webview_page.dart' as _i56; +import 'package:enaklo/sample/community_sample_data.dart' as _i59; +import 'package:enaklo/sample/news_sample_data.dart' as _i61; +import 'package:enaklo/sample/promo_sample_data.dart' as _i63; +import 'package:enaklo/sample/sample_data.dart' as _i60; +import 'package:enaklo/sample/shop_sample_data.dart' as _i64; +import 'package:flutter/material.dart' as _i58; /// generated route for /// [_i1.AccountMyPage] -class AccountMyRoute extends _i54.PageRouteInfo { - const AccountMyRoute({List<_i54.PageRouteInfo>? children}) +class AccountMyRoute extends _i57.PageRouteInfo { + const AccountMyRoute({List<_i57.PageRouteInfo>? children}) : super(AccountMyRoute.name, initialChildren: children); static const String name = 'AccountMyRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i1.AccountMyPage(); @@ -121,13 +127,13 @@ class AccountMyRoute extends _i54.PageRouteInfo { /// generated route for /// [_i2.AddressPage] -class AddressRoute extends _i54.PageRouteInfo { - const AddressRoute({List<_i54.PageRouteInfo>? children}) +class AddressRoute extends _i57.PageRouteInfo { + const AddressRoute({List<_i57.PageRouteInfo>? children}) : super(AddressRoute.name, initialChildren: children); static const String name = 'AddressRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i2.AddressPage(); @@ -137,13 +143,13 @@ class AddressRoute extends _i54.PageRouteInfo { /// generated route for /// [_i3.CoinHistoryPage] -class CoinHistoryRoute extends _i54.PageRouteInfo { - const CoinHistoryRoute({List<_i54.PageRouteInfo>? children}) +class CoinHistoryRoute extends _i57.PageRouteInfo { + const CoinHistoryRoute({List<_i57.PageRouteInfo>? children}) : super(CoinHistoryRoute.name, initialChildren: children); static const String name = 'CoinHistoryRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i3.CoinHistoryPage(); @@ -153,13 +159,13 @@ class CoinHistoryRoute extends _i54.PageRouteInfo { /// generated route for /// [_i4.CoinPage] -class CoinRoute extends _i54.PageRouteInfo { - const CoinRoute({List<_i54.PageRouteInfo>? children}) +class CoinRoute extends _i57.PageRouteInfo { + const CoinRoute({List<_i57.PageRouteInfo>? children}) : super(CoinRoute.name, initialChildren: children); static const String name = 'CoinRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i4.CoinPage(); @@ -169,12 +175,12 @@ class CoinRoute extends _i54.PageRouteInfo { /// generated route for /// [_i5.ComingSoonPage] -class ComingSoonRoute extends _i54.PageRouteInfo { +class ComingSoonRoute extends _i57.PageRouteInfo { ComingSoonRoute({ - _i55.Key? key, + _i58.Key? key, required String title, String? iconPath, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( ComingSoonRoute.name, args: ComingSoonRouteArgs(key: key, title: title, iconPath: iconPath), @@ -183,7 +189,7 @@ class ComingSoonRoute extends _i54.PageRouteInfo { static const String name = 'ComingSoonRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -199,7 +205,7 @@ class ComingSoonRoute extends _i54.PageRouteInfo { class ComingSoonRouteArgs { const ComingSoonRouteArgs({this.key, required this.title, this.iconPath}); - final _i55.Key? key; + final _i58.Key? key; final String title; @@ -226,11 +232,11 @@ class ComingSoonRouteArgs { /// generated route for /// [_i6.CommunityDetailPage] class CommunityDetailRoute - extends _i54.PageRouteInfo { + extends _i57.PageRouteInfo { CommunityDetailRoute({ - _i55.Key? key, - required _i56.CommunityGroup group, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i59.CommunityGroup group, + List<_i57.PageRouteInfo>? children, }) : super( CommunityDetailRoute.name, args: CommunityDetailRouteArgs(key: key, group: group), @@ -239,7 +245,7 @@ class CommunityDetailRoute static const String name = 'CommunityDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -251,9 +257,9 @@ class CommunityDetailRoute class CommunityDetailRouteArgs { const CommunityDetailRouteArgs({this.key, required this.group}); - final _i55.Key? key; + final _i58.Key? key; - final _i56.CommunityGroup group; + final _i59.CommunityGroup group; @override String toString() { @@ -273,13 +279,13 @@ class CommunityDetailRouteArgs { /// generated route for /// [_i7.CommunityPage] -class CommunityRoute extends _i54.PageRouteInfo { - const CommunityRoute({List<_i54.PageRouteInfo>? children}) +class CommunityRoute extends _i57.PageRouteInfo { + const CommunityRoute({List<_i57.PageRouteInfo>? children}) : super(CommunityRoute.name, initialChildren: children); static const String name = 'CommunityRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i7.CommunityPage(); @@ -290,11 +296,11 @@ class CommunityRoute extends _i54.PageRouteInfo { /// generated route for /// [_i8.CommunityPostDetailPage] class CommunityPostDetailRoute - extends _i54.PageRouteInfo { + extends _i57.PageRouteInfo { CommunityPostDetailRoute({ - _i55.Key? key, - required _i56.CommunityPost post, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i59.CommunityPost post, + List<_i57.PageRouteInfo>? children, }) : super( CommunityPostDetailRoute.name, args: CommunityPostDetailRouteArgs(key: key, post: post), @@ -303,7 +309,7 @@ class CommunityPostDetailRoute static const String name = 'CommunityPostDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -315,9 +321,9 @@ class CommunityPostDetailRoute class CommunityPostDetailRouteArgs { const CommunityPostDetailRouteArgs({this.key, required this.post}); - final _i55.Key? key; + final _i58.Key? key; - final _i56.CommunityPost post; + final _i59.CommunityPost post; @override String toString() { @@ -337,11 +343,11 @@ class CommunityPostDetailRouteArgs { /// generated route for /// [_i9.CreatePasswordPage] -class CreatePasswordRoute extends _i54.PageRouteInfo { +class CreatePasswordRoute extends _i57.PageRouteInfo { CreatePasswordRoute({ - _i55.Key? key, + _i58.Key? key, required String registrationToken, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( CreatePasswordRoute.name, args: CreatePasswordRouteArgs( @@ -353,11 +359,11 @@ class CreatePasswordRoute extends _i54.PageRouteInfo { static const String name = 'CreatePasswordRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i9.CreatePasswordPage( key: args.key, registrationToken: args.registrationToken, @@ -370,7 +376,7 @@ class CreatePasswordRoute extends _i54.PageRouteInfo { class CreatePasswordRouteArgs { const CreatePasswordRouteArgs({this.key, required this.registrationToken}); - final _i55.Key? key; + final _i58.Key? key; final String registrationToken; @@ -392,13 +398,13 @@ class CreatePasswordRouteArgs { /// generated route for /// [_i10.DrawDetailPage] -class DrawDetailRoute extends _i54.PageRouteInfo { - const DrawDetailRoute({List<_i54.PageRouteInfo>? children}) +class DrawDetailRoute extends _i57.PageRouteInfo { + const DrawDetailRoute({List<_i57.PageRouteInfo>? children}) : super(DrawDetailRoute.name, initialChildren: children); static const String name = 'DrawDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i10.DrawDetailPage(); @@ -408,13 +414,13 @@ class DrawDetailRoute extends _i54.PageRouteInfo { /// generated route for /// [_i11.DrawInfoPage] -class DrawInfoRoute extends _i54.PageRouteInfo { - const DrawInfoRoute({List<_i54.PageRouteInfo>? children}) +class DrawInfoRoute extends _i57.PageRouteInfo { + const DrawInfoRoute({List<_i57.PageRouteInfo>? children}) : super(DrawInfoRoute.name, initialChildren: children); static const String name = 'DrawInfoRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i11.DrawInfoPage(); @@ -424,13 +430,13 @@ class DrawInfoRoute extends _i54.PageRouteInfo { /// generated route for /// [_i12.DrawMyNumberPage] -class DrawMyNumberRoute extends _i54.PageRouteInfo { - const DrawMyNumberRoute({List<_i54.PageRouteInfo>? children}) +class DrawMyNumberRoute extends _i57.PageRouteInfo { + const DrawMyNumberRoute({List<_i57.PageRouteInfo>? children}) : super(DrawMyNumberRoute.name, initialChildren: children); static const String name = 'DrawMyNumberRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i12.DrawMyNumberPage(); @@ -440,13 +446,13 @@ class DrawMyNumberRoute extends _i54.PageRouteInfo { /// generated route for /// [_i13.DrawPage] -class DrawRoute extends _i54.PageRouteInfo { - const DrawRoute({List<_i54.PageRouteInfo>? children}) +class DrawRoute extends _i57.PageRouteInfo { + const DrawRoute({List<_i57.PageRouteInfo>? children}) : super(DrawRoute.name, initialChildren: children); static const String name = 'DrawRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i13.DrawPage(); @@ -456,13 +462,13 @@ class DrawRoute extends _i54.PageRouteInfo { /// generated route for /// [_i14.DrawTodayPage] -class DrawTodayRoute extends _i54.PageRouteInfo { - const DrawTodayRoute({List<_i54.PageRouteInfo>? children}) +class DrawTodayRoute extends _i57.PageRouteInfo { + const DrawTodayRoute({List<_i57.PageRouteInfo>? children}) : super(DrawTodayRoute.name, initialChildren: children); static const String name = 'DrawTodayRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i14.DrawTodayPage(); @@ -472,13 +478,13 @@ class DrawTodayRoute extends _i54.PageRouteInfo { /// generated route for /// [_i15.DrawWinnerPage] -class DrawWinnerRoute extends _i54.PageRouteInfo { - const DrawWinnerRoute({List<_i54.PageRouteInfo>? children}) +class DrawWinnerRoute extends _i57.PageRouteInfo { + const DrawWinnerRoute({List<_i57.PageRouteInfo>? children}) : super(DrawWinnerRoute.name, initialChildren: children); static const String name = 'DrawWinnerRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i15.DrawWinnerPage(); @@ -488,45 +494,45 @@ class DrawWinnerRoute extends _i54.PageRouteInfo { /// generated route for /// [_i16.ExchangePage] -class ExchangeRoute extends _i54.PageRouteInfo { - const ExchangeRoute({List<_i54.PageRouteInfo>? children}) +class ExchangeRoute extends _i57.PageRouteInfo { + const ExchangeRoute({List<_i57.PageRouteInfo>? children}) : super(ExchangeRoute.name, initialChildren: children); static const String name = 'ExchangeRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i16.ExchangePage()); + return _i57.WrappedRoute(child: const _i16.ExchangePage()); }, ); } /// generated route for /// [_i17.FerrisWheelPage] -class FerrisWheelRoute extends _i54.PageRouteInfo { - const FerrisWheelRoute({List<_i54.PageRouteInfo>? children}) +class FerrisWheelRoute extends _i57.PageRouteInfo { + const FerrisWheelRoute({List<_i57.PageRouteInfo>? children}) : super(FerrisWheelRoute.name, initialChildren: children); static const String name = 'FerrisWheelRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i17.FerrisWheelPage()); + return _i57.WrappedRoute(child: const _i17.FerrisWheelPage()); }, ); } /// generated route for /// [_i18.HomePage] -class HomeRoute extends _i54.PageRouteInfo { - const HomeRoute({List<_i54.PageRouteInfo>? children}) +class HomeRoute extends _i57.PageRouteInfo { + const HomeRoute({List<_i57.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i18.HomePage(); @@ -536,29 +542,29 @@ class HomeRoute extends _i54.PageRouteInfo { /// generated route for /// [_i19.LoginPage] -class LoginRoute extends _i54.PageRouteInfo { - const LoginRoute({List<_i54.PageRouteInfo>? children}) +class LoginRoute extends _i57.PageRouteInfo { + const LoginRoute({List<_i57.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i19.LoginPage()); + return _i57.WrappedRoute(child: const _i19.LoginPage()); }, ); } /// generated route for /// [_i20.MainPage] -class MainRoute extends _i54.PageRouteInfo { - const MainRoute({List<_i54.PageRouteInfo>? children}) +class MainRoute extends _i57.PageRouteInfo { + const MainRoute({List<_i57.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i20.MainPage(); @@ -568,11 +574,11 @@ class MainRoute extends _i54.PageRouteInfo { /// generated route for /// [_i21.MerchantDetailPage] -class MerchantDetailRoute extends _i54.PageRouteInfo { +class MerchantDetailRoute extends _i57.PageRouteInfo { MerchantDetailRoute({ - _i55.Key? key, - required _i57.MerchantModel merchant, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i60.MerchantModel merchant, + List<_i57.PageRouteInfo>? children, }) : super( MerchantDetailRoute.name, args: MerchantDetailRouteArgs(key: key, merchant: merchant), @@ -581,7 +587,7 @@ class MerchantDetailRoute extends _i54.PageRouteInfo { static const String name = 'MerchantDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -593,9 +599,9 @@ class MerchantDetailRoute extends _i54.PageRouteInfo { class MerchantDetailRouteArgs { const MerchantDetailRouteArgs({this.key, required this.merchant}); - final _i55.Key? key; + final _i58.Key? key; - final _i57.MerchantModel merchant; + final _i60.MerchantModel merchant; @override String toString() { @@ -615,13 +621,13 @@ class MerchantDetailRouteArgs { /// generated route for /// [_i22.MerchantPage] -class MerchantRoute extends _i54.PageRouteInfo { - const MerchantRoute({List<_i54.PageRouteInfo>? children}) +class MerchantRoute extends _i57.PageRouteInfo { + const MerchantRoute({List<_i57.PageRouteInfo>? children}) : super(MerchantRoute.name, initialChildren: children); static const String name = 'MerchantRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i22.MerchantPage(); @@ -631,13 +637,13 @@ class MerchantRoute extends _i54.PageRouteInfo { /// generated route for /// [_i23.MisteryBoxPage] -class MisteryBoxRoute extends _i54.PageRouteInfo { - const MisteryBoxRoute({List<_i54.PageRouteInfo>? children}) +class MisteryBoxRoute extends _i57.PageRouteInfo { + const MisteryBoxRoute({List<_i57.PageRouteInfo>? children}) : super(MisteryBoxRoute.name, initialChildren: children); static const String name = 'MisteryBoxRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i23.MisteryBoxPage(); @@ -647,11 +653,11 @@ class MisteryBoxRoute extends _i54.PageRouteInfo { /// generated route for /// [_i24.NewsDetailPage] -class NewsDetailRoute extends _i54.PageRouteInfo { +class NewsDetailRoute extends _i57.PageRouteInfo { NewsDetailRoute({ - _i55.Key? key, - required _i58.NewsArticle article, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i61.NewsArticle article, + List<_i57.PageRouteInfo>? children, }) : super( NewsDetailRoute.name, args: NewsDetailRouteArgs(key: key, article: article), @@ -660,7 +666,7 @@ class NewsDetailRoute extends _i54.PageRouteInfo { static const String name = 'NewsDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -672,9 +678,9 @@ class NewsDetailRoute extends _i54.PageRouteInfo { class NewsDetailRouteArgs { const NewsDetailRouteArgs({this.key, required this.article}); - final _i55.Key? key; + final _i58.Key? key; - final _i58.NewsArticle article; + final _i61.NewsArticle article; @override String toString() { @@ -694,13 +700,13 @@ class NewsDetailRouteArgs { /// generated route for /// [_i25.NewsPage] -class NewsRoute extends _i54.PageRouteInfo { - const NewsRoute({List<_i54.PageRouteInfo>? children}) +class NewsRoute extends _i57.PageRouteInfo { + const NewsRoute({List<_i57.PageRouteInfo>? children}) : super(NewsRoute.name, initialChildren: children); static const String name = 'NewsRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i25.NewsPage(); @@ -710,13 +716,13 @@ class NewsRoute extends _i54.PageRouteInfo { /// generated route for /// [_i26.NotificationPage] -class NotificationRoute extends _i54.PageRouteInfo { - const NotificationRoute({List<_i54.PageRouteInfo>? children}) +class NotificationRoute extends _i57.PageRouteInfo { + const NotificationRoute({List<_i57.PageRouteInfo>? children}) : super(NotificationRoute.name, initialChildren: children); static const String name = 'NotificationRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i26.NotificationPage(); @@ -726,13 +732,13 @@ class NotificationRoute extends _i54.PageRouteInfo { /// generated route for /// [_i27.OnboardingPage] -class OnboardingRoute extends _i54.PageRouteInfo { - const OnboardingRoute({List<_i54.PageRouteInfo>? children}) +class OnboardingRoute extends _i57.PageRouteInfo { + const OnboardingRoute({List<_i57.PageRouteInfo>? children}) : super(OnboardingRoute.name, initialChildren: children); static const String name = 'OnboardingRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i27.OnboardingPage(); @@ -742,11 +748,11 @@ class OnboardingRoute extends _i54.PageRouteInfo { /// generated route for /// [_i28.OrderDetailPage] -class OrderDetailRoute extends _i54.PageRouteInfo { +class OrderDetailRoute extends _i57.PageRouteInfo { OrderDetailRoute({ - _i55.Key? key, - required _i59.CustomerOrder order, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i62.CustomerOrder order, + List<_i57.PageRouteInfo>? children, }) : super( OrderDetailRoute.name, args: OrderDetailRouteArgs(key: key, order: order), @@ -755,11 +761,11 @@ class OrderDetailRoute extends _i54.PageRouteInfo { static const String name = 'OrderDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i28.OrderDetailPage(key: args.key, order: args.order), ); }, @@ -769,9 +775,9 @@ class OrderDetailRoute extends _i54.PageRouteInfo { class OrderDetailRouteArgs { const OrderDetailRouteArgs({this.key, required this.order}); - final _i55.Key? key; + final _i58.Key? key; - final _i59.CustomerOrder order; + final _i62.CustomerOrder order; @override String toString() { @@ -791,28 +797,28 @@ class OrderDetailRouteArgs { /// generated route for /// [_i29.OrderPage] -class OrderRoute extends _i54.PageRouteInfo { - const OrderRoute({List<_i54.PageRouteInfo>? children}) +class OrderRoute extends _i57.PageRouteInfo { + const OrderRoute({List<_i57.PageRouteInfo>? children}) : super(OrderRoute.name, initialChildren: children); static const String name = 'OrderRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i29.OrderPage()); + return _i57.WrappedRoute(child: const _i29.OrderPage()); }, ); } /// generated route for /// [_i30.OtpPage] -class OtpRoute extends _i54.PageRouteInfo { +class OtpRoute extends _i57.PageRouteInfo { OtpRoute({ - _i55.Key? key, + _i58.Key? key, required String registrationToken, required String phoneNumber, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( OtpRoute.name, args: OtpRouteArgs( @@ -825,11 +831,11 @@ class OtpRoute extends _i54.PageRouteInfo { static const String name = 'OtpRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i30.OtpPage( key: args.key, registrationToken: args.registrationToken, @@ -847,7 +853,7 @@ class OtpRouteArgs { required this.phoneNumber, }); - final _i55.Key? key; + final _i58.Key? key; final String registrationToken; @@ -874,13 +880,13 @@ class OtpRouteArgs { /// generated route for /// [_i31.OutletPage] -class OutletRoute extends _i54.PageRouteInfo { - const OutletRoute({List<_i54.PageRouteInfo>? children}) +class OutletRoute extends _i57.PageRouteInfo { + const OutletRoute({List<_i57.PageRouteInfo>? children}) : super(OutletRoute.name, initialChildren: children); static const String name = 'OutletRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i31.OutletPage(); @@ -890,11 +896,11 @@ class OutletRoute extends _i54.PageRouteInfo { /// generated route for /// [_i32.PasswordPage] -class PasswordRoute extends _i54.PageRouteInfo { +class PasswordRoute extends _i57.PageRouteInfo { PasswordRoute({ - _i55.Key? key, + _i58.Key? key, required String phoneNumber, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( PasswordRoute.name, args: PasswordRouteArgs(key: key, phoneNumber: phoneNumber), @@ -903,11 +909,11 @@ class PasswordRoute extends _i54.PageRouteInfo { static const String name = 'PasswordRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i32.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), ); }, @@ -917,7 +923,7 @@ class PasswordRoute extends _i54.PageRouteInfo { class PasswordRouteArgs { const PasswordRouteArgs({this.key, required this.phoneNumber}); - final _i55.Key? key; + final _i58.Key? key; final String phoneNumber; @@ -939,29 +945,29 @@ class PasswordRouteArgs { /// generated route for /// [_i33.PaymentCodePage] -class PaymentCodeRoute extends _i54.PageRouteInfo { - const PaymentCodeRoute({List<_i54.PageRouteInfo>? children}) +class PaymentCodeRoute extends _i57.PageRouteInfo { + const PaymentCodeRoute({List<_i57.PageRouteInfo>? children}) : super(PaymentCodeRoute.name, initialChildren: children); static const String name = 'PaymentCodeRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i33.PaymentCodePage()); + return _i57.WrappedRoute(child: const _i33.PaymentCodePage()); }, ); } /// generated route for /// [_i34.PaymentPage] -class PaymentRoute extends _i54.PageRouteInfo { - const PaymentRoute({List<_i54.PageRouteInfo>? children}) +class PaymentRoute extends _i57.PageRouteInfo { + const PaymentRoute({List<_i57.PageRouteInfo>? children}) : super(PaymentRoute.name, initialChildren: children); static const String name = 'PaymentRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i34.PaymentPage(); @@ -971,28 +977,28 @@ class PaymentRoute extends _i54.PageRouteInfo { /// generated route for /// [_i35.PinChangePage] -class PinChangeRoute extends _i54.PageRouteInfo { - const PinChangeRoute({List<_i54.PageRouteInfo>? children}) +class PinChangeRoute extends _i57.PageRouteInfo { + const PinChangeRoute({List<_i57.PageRouteInfo>? children}) : super(PinChangeRoute.name, initialChildren: children); static const String name = 'PinChangeRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i35.PinChangePage()); + return _i57.WrappedRoute(child: const _i35.PinChangePage()); }, ); } /// generated route for /// [_i36.PinPage] -class PinRoute extends _i54.PageRouteInfo { +class PinRoute extends _i57.PageRouteInfo { PinRoute({ - _i55.Key? key, + _i58.Key? key, bool isCreatePin = true, String? title, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( PinRoute.name, args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title), @@ -1001,7 +1007,7 @@ class PinRoute extends _i54.PageRouteInfo { static const String name = 'PinRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -1019,7 +1025,7 @@ class PinRoute extends _i54.PageRouteInfo { class PinRouteArgs { const PinRouteArgs({this.key, this.isCreatePin = true, this.title}); - final _i55.Key? key; + final _i58.Key? key; final bool isCreatePin; @@ -1045,13 +1051,13 @@ class PinRouteArgs { /// generated route for /// [_i37.PinSettingsPage] -class PinSettingsRoute extends _i54.PageRouteInfo { - const PinSettingsRoute({List<_i54.PageRouteInfo>? children}) +class PinSettingsRoute extends _i57.PageRouteInfo { + const PinSettingsRoute({List<_i57.PageRouteInfo>? children}) : super(PinSettingsRoute.name, initialChildren: children); static const String name = 'PinSettingsRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i37.PinSettingsPage(); @@ -1061,11 +1067,11 @@ class PinSettingsRoute extends _i54.PageRouteInfo { /// generated route for /// [_i38.PinSetupPage] -class PinSetupRoute extends _i54.PageRouteInfo { +class PinSetupRoute extends _i57.PageRouteInfo { PinSetupRoute({ - _i55.Key? key, - required _i59.PinOtpPurpose purpose, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i62.PinOtpPurpose purpose, + List<_i57.PageRouteInfo>? children, }) : super( PinSetupRoute.name, args: PinSetupRouteArgs(key: key, purpose: purpose), @@ -1074,11 +1080,11 @@ class PinSetupRoute extends _i54.PageRouteInfo { static const String name = 'PinSetupRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i38.PinSetupPage(key: args.key, purpose: args.purpose), ); }, @@ -1088,9 +1094,9 @@ class PinSetupRoute extends _i54.PageRouteInfo { class PinSetupRouteArgs { const PinSetupRouteArgs({this.key, required this.purpose}); - final _i55.Key? key; + final _i58.Key? key; - final _i59.PinOtpPurpose purpose; + final _i62.PinOtpPurpose purpose; @override String toString() { @@ -1110,13 +1116,13 @@ class PinSetupRouteArgs { /// generated route for /// [_i39.PointPage] -class PointRoute extends _i54.PageRouteInfo { - const PointRoute({List<_i54.PageRouteInfo>? children}) +class PointRoute extends _i57.PageRouteInfo { + const PointRoute({List<_i57.PageRouteInfo>? children}) : super(PointRoute.name, initialChildren: children); static const String name = 'PointRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i39.PointPage(); @@ -1126,12 +1132,12 @@ class PointRoute extends _i54.PageRouteInfo { /// generated route for /// [_i40.ProductRedeemPage] -class ProductRedeemRoute extends _i54.PageRouteInfo { +class ProductRedeemRoute extends _i57.PageRouteInfo { ProductRedeemRoute({ - _i55.Key? key, + _i58.Key? key, required _i4.Product product, required _i4.PointCard pointCard, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( ProductRedeemRoute.name, args: ProductRedeemRouteArgs( @@ -1144,7 +1150,7 @@ class ProductRedeemRoute extends _i54.PageRouteInfo { static const String name = 'ProductRedeemRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1164,7 +1170,7 @@ class ProductRedeemRouteArgs { required this.pointCard, }); - final _i55.Key? key; + final _i58.Key? key; final _i4.Product product; @@ -1190,27 +1196,27 @@ class ProductRedeemRouteArgs { /// generated route for /// [_i41.ProfilePage] -class ProfileRoute extends _i54.PageRouteInfo { - const ProfileRoute({List<_i54.PageRouteInfo>? children}) +class ProfileRoute extends _i57.PageRouteInfo { + const ProfileRoute({List<_i57.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i41.ProfilePage()); + return _i57.WrappedRoute(child: const _i41.ProfilePage()); }, ); } /// generated route for /// [_i42.PromoDetailPage] -class PromoDetailRoute extends _i54.PageRouteInfo { +class PromoDetailRoute extends _i57.PageRouteInfo { PromoDetailRoute({ - _i55.Key? key, - required _i60.Promo promo, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + required _i63.Promo promo, + List<_i57.PageRouteInfo>? children, }) : super( PromoDetailRoute.name, args: PromoDetailRouteArgs(key: key, promo: promo), @@ -1219,7 +1225,7 @@ class PromoDetailRoute extends _i54.PageRouteInfo { static const String name = 'PromoDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1231,9 +1237,9 @@ class PromoDetailRoute extends _i54.PageRouteInfo { class PromoDetailRouteArgs { const PromoDetailRouteArgs({this.key, required this.promo}); - final _i55.Key? key; + final _i58.Key? key; - final _i60.Promo promo; + final _i63.Promo promo; @override String toString() { @@ -1253,13 +1259,13 @@ class PromoDetailRouteArgs { /// generated route for /// [_i43.PromoPage] -class PromoRoute extends _i54.PageRouteInfo { - const PromoRoute({List<_i54.PageRouteInfo>? children}) +class PromoRoute extends _i57.PageRouteInfo { + const PromoRoute({List<_i57.PageRouteInfo>? children}) : super(PromoRoute.name, initialChildren: children); static const String name = 'PromoRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i43.PromoPage(); @@ -1269,11 +1275,11 @@ class PromoRoute extends _i54.PageRouteInfo { /// generated route for /// [_i44.RegisterPage] -class RegisterRoute extends _i54.PageRouteInfo { +class RegisterRoute extends _i57.PageRouteInfo { RegisterRoute({ - _i55.Key? key, + _i58.Key? key, required String phoneNumber, - List<_i54.PageRouteInfo>? children, + List<_i57.PageRouteInfo>? children, }) : super( RegisterRoute.name, args: RegisterRouteArgs(key: key, phoneNumber: phoneNumber), @@ -1282,11 +1288,11 @@ class RegisterRoute extends _i54.PageRouteInfo { static const String name = 'RegisterRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i54.WrappedRoute( + return _i57.WrappedRoute( child: _i44.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), ); }, @@ -1296,7 +1302,7 @@ class RegisterRoute extends _i54.PageRouteInfo { class RegisterRouteArgs { const RegisterRouteArgs({this.key, required this.phoneNumber}); - final _i55.Key? key; + final _i58.Key? key; final String phoneNumber; @@ -1318,13 +1324,13 @@ class RegisterRouteArgs { /// generated route for /// [_i45.RewardPage] -class RewardRoute extends _i54.PageRouteInfo { - const RewardRoute({List<_i54.PageRouteInfo>? children}) +class RewardRoute extends _i57.PageRouteInfo { + const RewardRoute({List<_i57.PageRouteInfo>? children}) : super(RewardRoute.name, initialChildren: children); static const String name = 'RewardRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { return const _i45.RewardPage(); @@ -1333,92 +1339,222 @@ class RewardRoute extends _i54.PageRouteInfo { } /// generated route for -/// [_i46.SplashPage] -class SplashRoute extends _i54.PageRouteInfo { - const SplashRoute({List<_i54.PageRouteInfo>? children}) +/// [_i46.ShopCategoryPage] +class ShopCategoryRoute extends _i57.PageRouteInfo { + ShopCategoryRoute({ + _i58.Key? key, + _i64.ShopCategory? category, + bool promoOnly = false, + List<_i57.PageRouteInfo>? children, + }) : super( + ShopCategoryRoute.name, + args: ShopCategoryRouteArgs( + key: key, + category: category, + promoOnly: promoOnly, + ), + initialChildren: children, + ); + + static const String name = 'ShopCategoryRoute'; + + static _i57.PageInfo page = _i57.PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const ShopCategoryRouteArgs(), + ); + return _i46.ShopCategoryPage( + key: args.key, + category: args.category, + promoOnly: args.promoOnly, + ); + }, + ); +} + +class ShopCategoryRouteArgs { + const ShopCategoryRouteArgs({ + this.key, + this.category, + this.promoOnly = false, + }); + + final _i58.Key? key; + + final _i64.ShopCategory? category; + + final bool promoOnly; + + @override + String toString() { + return 'ShopCategoryRouteArgs{key: $key, category: $category, promoOnly: $promoOnly}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! ShopCategoryRouteArgs) return false; + return key == other.key && + category == other.category && + promoOnly == other.promoOnly; + } + + @override + int get hashCode => key.hashCode ^ category.hashCode ^ promoOnly.hashCode; +} + +/// generated route for +/// [_i47.ShopPage] +class ShopRoute extends _i57.PageRouteInfo { + const ShopRoute({List<_i57.PageRouteInfo>? children}) + : super(ShopRoute.name, initialChildren: children); + + static const String name = 'ShopRoute'; + + static _i57.PageInfo page = _i57.PageInfo( + name, + builder: (data) { + return const _i47.ShopPage(); + }, + ); +} + +/// generated route for +/// [_i48.ShopProductDetailPage] +class ShopProductDetailRoute + extends _i57.PageRouteInfo { + ShopProductDetailRoute({ + _i58.Key? key, + required _i64.ShopProduct product, + List<_i57.PageRouteInfo>? children, + }) : super( + ShopProductDetailRoute.name, + args: ShopProductDetailRouteArgs(key: key, product: product), + initialChildren: children, + ); + + static const String name = 'ShopProductDetailRoute'; + + static _i57.PageInfo page = _i57.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i48.ShopProductDetailPage(key: args.key, product: args.product); + }, + ); +} + +class ShopProductDetailRouteArgs { + const ShopProductDetailRouteArgs({this.key, required this.product}); + + final _i58.Key? key; + + final _i64.ShopProduct product; + + @override + String toString() { + return 'ShopProductDetailRouteArgs{key: $key, product: $product}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! ShopProductDetailRouteArgs) return false; + return key == other.key && product == other.product; + } + + @override + int get hashCode => key.hashCode ^ product.hashCode; +} + +/// generated route for +/// [_i49.SplashPage] +class SplashRoute extends _i57.PageRouteInfo { + const SplashRoute({List<_i57.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i46.SplashPage(); + return const _i49.SplashPage(); }, ); } /// generated route for -/// [_i47.TransferPage] -class TransferRoute extends _i54.PageRouteInfo { - const TransferRoute({List<_i54.PageRouteInfo>? children}) +/// [_i50.TransferPage] +class TransferRoute extends _i57.PageRouteInfo { + const TransferRoute({List<_i57.PageRouteInfo>? children}) : super(TransferRoute.name, initialChildren: children); static const String name = 'TransferRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i47.TransferPage()); + return _i57.WrappedRoute(child: const _i50.TransferPage()); }, ); } /// generated route for -/// [_i48.VoucherDetailPage] -class VoucherDetailRoute extends _i54.PageRouteInfo { - const VoucherDetailRoute({List<_i54.PageRouteInfo>? children}) +/// [_i51.VoucherDetailPage] +class VoucherDetailRoute extends _i57.PageRouteInfo { + const VoucherDetailRoute({List<_i57.PageRouteInfo>? children}) : super(VoucherDetailRoute.name, initialChildren: children); static const String name = 'VoucherDetailRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i48.VoucherDetailPage(); + return const _i51.VoucherDetailPage(); }, ); } /// generated route for -/// [_i49.VoucherPage] -class VoucherRoute extends _i54.PageRouteInfo { - const VoucherRoute({List<_i54.PageRouteInfo>? children}) +/// [_i52.VoucherPage] +class VoucherRoute extends _i57.PageRouteInfo { + const VoucherRoute({List<_i57.PageRouteInfo>? children}) : super(VoucherRoute.name, initialChildren: children); static const String name = 'VoucherRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i49.VoucherPage(); + return const _i52.VoucherPage(); }, ); } /// generated route for -/// [_i50.WalletExpiringPage] -class WalletExpiringRoute extends _i54.PageRouteInfo { - const WalletExpiringRoute({List<_i54.PageRouteInfo>? children}) +/// [_i53.WalletExpiringPage] +class WalletExpiringRoute extends _i57.PageRouteInfo { + const WalletExpiringRoute({List<_i57.PageRouteInfo>? children}) : super(WalletExpiringRoute.name, initialChildren: children); static const String name = 'WalletExpiringRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i54.WrappedRoute(child: const _i50.WalletExpiringPage()); + return _i57.WrappedRoute(child: const _i53.WalletExpiringPage()); }, ); } /// generated route for -/// [_i51.WalletHistoryPage] -class WalletHistoryRoute extends _i54.PageRouteInfo { +/// [_i54.WalletHistoryPage] +class WalletHistoryRoute extends _i57.PageRouteInfo { WalletHistoryRoute({ - _i55.Key? key, - _i59.WalletCurrency initialCurrency = _i59.WalletCurrency.point, - List<_i54.PageRouteInfo>? children, + _i58.Key? key, + _i62.WalletCurrency initialCurrency = _i62.WalletCurrency.point, + List<_i57.PageRouteInfo>? children, }) : super( WalletHistoryRoute.name, args: WalletHistoryRouteArgs( @@ -1430,14 +1566,14 @@ class WalletHistoryRoute extends _i54.PageRouteInfo { static const String name = 'WalletHistoryRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const WalletHistoryRouteArgs(), ); - return _i54.WrappedRoute( - child: _i51.WalletHistoryPage( + return _i57.WrappedRoute( + child: _i54.WalletHistoryPage( key: args.key, initialCurrency: args.initialCurrency, ), @@ -1449,12 +1585,12 @@ class WalletHistoryRoute extends _i54.PageRouteInfo { class WalletHistoryRouteArgs { const WalletHistoryRouteArgs({ this.key, - this.initialCurrency = _i59.WalletCurrency.point, + this.initialCurrency = _i62.WalletCurrency.point, }); - final _i55.Key? key; + final _i58.Key? key; - final _i59.WalletCurrency initialCurrency; + final _i62.WalletCurrency initialCurrency; @override String toString() { @@ -1473,31 +1609,31 @@ class WalletHistoryRouteArgs { } /// generated route for -/// [_i52.WalletPage] -class WalletRoute extends _i54.PageRouteInfo { - const WalletRoute({List<_i54.PageRouteInfo>? children}) +/// [_i55.WalletPage] +class WalletRoute extends _i57.PageRouteInfo { + const WalletRoute({List<_i57.PageRouteInfo>? children}) : super(WalletRoute.name, initialChildren: children); static const String name = 'WalletRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i52.WalletPage(); + return const _i55.WalletPage(); }, ); } /// generated route for -/// [_i53.WebViewPage] -class WebViewRoute extends _i54.PageRouteInfo { +/// [_i56.WebViewPage] +class WebViewRoute extends _i57.PageRouteInfo { WebViewRoute({ - _i55.Key? key, + _i58.Key? key, required String title, required String url, bool fullscreen = false, - _i55.Color? backgroundColor, - List<_i54.PageRouteInfo>? children, + _i58.Color? backgroundColor, + List<_i57.PageRouteInfo>? children, }) : super( WebViewRoute.name, args: WebViewRouteArgs( @@ -1512,11 +1648,11 @@ class WebViewRoute extends _i54.PageRouteInfo { static const String name = 'WebViewRoute'; - static _i54.PageInfo page = _i54.PageInfo( + static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i53.WebViewPage( + return _i56.WebViewPage( key: args.key, title: args.title, url: args.url, @@ -1536,7 +1672,7 @@ class WebViewRouteArgs { this.backgroundColor, }); - final _i55.Key? key; + final _i58.Key? key; final String title; @@ -1544,7 +1680,7 @@ class WebViewRouteArgs { final bool fullscreen; - final _i55.Color? backgroundColor; + final _i58.Color? backgroundColor; @override String toString() { diff --git a/lib/sample/shop_sample_data.dart b/lib/sample/shop_sample_data.dart new file mode 100644 index 0000000..555061d --- /dev/null +++ b/lib/sample/shop_sample_data.dart @@ -0,0 +1,368 @@ +// Models +// +// EnakShop adalah toko online. Tahap ini baru katalog; keranjang dan checkout +// menyusul setelah API toko tersedia. +enum ShopCategory { + frozen('Makanan Beku', '๐ŸฅŸ'), + sauce('Sambal & Bumbu', '๐ŸŒถ๏ธ'), + coffee('Kopi & Minuman', 'โ˜•'), + noodle('Mie & Ramen', '๐Ÿœ'), + snack('Camilan', '๐Ÿช'), + merch('Merchandise', '๐Ÿ›๏ธ'), + hampers('Hampers', '๐ŸŽ'); + + const ShopCategory(this.label, this.emoji); + + final String label; + final String emoji; +} + +/// Label pojok kartu produk. +enum ShopTag { + bestSeller('Terlaris'), + newArrival('Baru'); + + const ShopTag(this.label); + + final String label; +} + +class ShopProduct { + final String id; + final String name; + final ShopCategory category; + final int price; + + /// Harga setelah diskon; `null` bila tidak sedang diskon. + final int? discountPrice; + final String emoji; + + /// Warna latar gambar produk, ARGB. + final int tint; + + /// Berat atau isi, mis. "500 g" atau "Isi 25 butir". + final String unit; + final String description; + + /// Judul pilihan varian, mis. "Ukuran" atau "Level pedas". + final String variantLabel; + final List variants; + final int soldCount; + final int stock; + final ShopTag? tag; + final DateTime addedAt; + + const ShopProduct({ + required this.id, + required this.name, + required this.category, + required this.price, + required this.emoji, + required this.tint, + required this.unit, + required this.description, + required this.soldCount, + required this.stock, + required this.addedAt, + this.discountPrice, + this.variantLabel = 'Varian', + this.variants = const [], + this.tag, + }); + + bool get isDiscounted => discountPrice != null; + + int get finalPrice => discountPrice ?? price; + + /// Persen potongan, dibulatkan. + int get discountPercent => + isDiscounted ? ((price - discountPrice!) * 100 / price).round() : 0; +} + +/// Banner promo di beranda toko; mengarah ke satu kategori. +class ShopPromo { + final String id; + final String title; + final String subtitle; + final ShopCategory category; + + /// Warna gradasi banner, ARGB. + final List gradient; + + const ShopPromo({ + required this.id, + required this.title, + required this.subtitle, + required this.category, + required this.gradient, + }); +} + +// Sample Data +DateTime _daysAgo(int days) => DateTime.now().subtract(Duration(days: days)); + +/// Getter, bukan `final`, supaya perubahan data langsung terbaca saat hot +/// reload (variabel global yang sudah terisi tidak diisi ulang). +List get shopPromoSamples => const [ + ShopPromo( + id: 'sp1', + title: 'Diskon s.d. 20% Makanan Beku', + subtitle: 'Stok bakso & dimsum di rumah', + category: ShopCategory.frozen, + gradient: [0xFFD90000, 0xFF990000], + ), + ShopPromo( + id: 'sp2', + title: 'Hampers Mulai Rp 299 Ribu', + subtitle: 'Kirim rasa Nusantara untuk orang tersayang', + category: ShopCategory.hampers, + gradient: [0xFF43A047, 0xFF1B5E20], + ), + ShopPromo( + id: 'sp3', + title: 'Merchandise Diskon s.d. 15%', + subtitle: 'Tumbler, kaos, dan tote bag Enaklo', + category: ShopCategory.merch, + gradient: [0xFFF59E0B, 0xFFC2410C], + ), +]; + +List get shopSamples => [ + ShopProduct( + id: 's1', + name: 'Bakso Sapi Frozen Bakso 343', + category: ShopCategory.frozen, + price: 65000, + discountPrice: 52000, + emoji: '๐Ÿฒ', + tint: 0xFFF6D5D0, + unit: 'Isi 25 butir', + variantLabel: 'Ukuran', + variants: ['Isi 25 butir', 'Isi 50 butir'], + description: + 'Bakso sapi resep Bakso 343, dibekukan segera setelah dibuat. Cukup ' + 'rebus 5 menit. Tahan 3 bulan di freezer.', + soldCount: 320, + stock: 48, + tag: ShopTag.bestSeller, + addedAt: _daysAgo(60), + ), + ShopProduct( + id: 's2', + name: 'Dimsum Ayam Frozen', + category: ShopCategory.frozen, + price: 48000, + emoji: '๐ŸฅŸ', + tint: 0xFFF3E6CF, + unit: 'Isi 20 pcs', + description: + 'Dimsum ayam udang dengan kulit tipis. Kukus 10 menit dari beku, ' + 'sajikan dengan sambal.', + soldCount: 85, + stock: 30, + tag: ShopTag.newArrival, + addedAt: _daysAgo(5), + ), + ShopProduct( + id: 's3', + name: 'Sambal Woku Pedas', + category: ShopCategory.sauce, + price: 35000, + discountPrice: 29000, + emoji: '๐ŸŒถ๏ธ', + tint: 0xFFFBDCC8, + unit: '200 g', + variantLabel: 'Level pedas', + variants: ['Level 2', 'Level 3', 'Level 5'], + description: + 'Sambal khas Woku Pedas dengan cabai, kemangi, dan rempah Manado. ' + 'Tanpa pengawet, simpan di kulkas setelah dibuka.', + soldCount: 540, + stock: 120, + tag: ShopTag.bestSeller, + addedAt: _daysAgo(90), + ), + ShopProduct( + id: 's4', + name: 'Bumbu Rica-Rica Instan', + category: ShopCategory.sauce, + price: 18000, + emoji: '๐Ÿง‚', + tint: 0xFFF7E2C8, + unit: '100 g', + description: + 'Bumbu rica-rica siap pakai untuk 1 kg ayam atau ikan. Tinggal tumis ' + 'dan masak bersama bahan utama.', + soldCount: 140, + stock: 75, + addedAt: _daysAgo(40), + ), + ShopProduct( + id: 's5', + name: 'Kopi Tumulu House Blend', + category: ShopCategory.coffee, + price: 85000, + emoji: 'โ˜•', + tint: 0xFFE8DCCF, + unit: '250 g', + variantLabel: 'Gilingan', + variants: ['Biji utuh', 'Bubuk halus', 'Bubuk kasar'], + description: + 'Campuran arabika Gayo dan robusta Temanggung yang dipakai di Tumulu ' + 'Coffee. Cocok untuk kopi susu maupun kopi tubruk.', + soldCount: 410, + stock: 64, + tag: ShopTag.bestSeller, + addedAt: _daysAgo(120), + ), + ShopProduct( + id: 's6', + name: 'Cold Brew Tumulu 1 Liter', + category: ShopCategory.coffee, + price: 75000, + discountPrice: 65000, + emoji: '๐Ÿง‹', + tint: 0xFFE2D6CC, + unit: '1 liter', + description: + 'Kopi seduh dingin 18 jam, tinggal tuang ke es dan susu. Simpan di ' + 'kulkas, habiskan dalam 7 hari.', + soldCount: 95, + stock: 20, + addedAt: _daysAgo(20), + ), + ShopProduct( + id: 's7', + name: 'Ramen Kit Shoyu Ramenmulu', + category: ShopCategory.noodle, + price: 79000, + discountPrice: 69000, + emoji: '๐Ÿœ', + tint: 0xFFF5E1C8, + unit: '2 porsi', + description: + 'Mie ramen segar, kuah shoyu pekat, chashu, dan telur ajitama untuk ' + '2 porsi. Siap dalam 10 menit.', + soldCount: 150, + stock: 18, + tag: ShopTag.newArrival, + addedAt: _daysAgo(3), + ), + ShopProduct( + id: 's8', + name: 'Mie Ramen Kering', + category: ShopCategory.noodle, + price: 32000, + emoji: '๐Ÿ', + tint: 0xFFF4EBD3, + unit: '500 g', + description: + 'Mie ramen kering untuk 5 porsi, kenyal seperti mie segar. Rebus ' + '3 menit.', + soldCount: 60, + stock: 90, + addedAt: _daysAgo(30), + ), + ShopProduct( + id: 's9', + name: 'Keripik Kulit Ikan Pedas', + category: ShopCategory.snack, + price: 28000, + emoji: '๐Ÿ˜', + tint: 0xFFF6E7C1, + unit: '100 g', + description: + 'Keripik kulit ikan renyah dengan bumbu woku pedas. Camilan pas ' + 'teman nonton.', + soldCount: 210, + stock: 55, + addedAt: _daysAgo(50), + ), + ShopProduct( + id: 's10', + name: 'Nastar Keju Premium', + category: ShopCategory.snack, + price: 95000, + discountPrice: 85000, + emoji: '๐Ÿช', + tint: 0xFFF7E4B5, + unit: 'Toples 500 g', + description: + 'Nastar selai nanas homemade dengan taburan keju edam. Tahan 1 bulan ' + 'dalam toples tertutup.', + soldCount: 130, + stock: 25, + addedAt: _daysAgo(15), + ), + ShopProduct( + id: 's11', + name: 'Tumbler Enaklo 500 ml', + category: ShopCategory.merch, + price: 129000, + emoji: '๐Ÿฅค', + tint: 0xFFF3D3D3, + unit: '500 ml', + variantLabel: 'Warna', + variants: ['Merah', 'Hitam', 'Krem'], + description: + 'Tumbler stainless dua lapis yang menjaga minuman tetap dingin sampai ' + '12 jam. Tutup anti tumpah.', + soldCount: 75, + stock: 40, + tag: ShopTag.bestSeller, + addedAt: _daysAgo(70), + ), + ShopProduct( + id: 's12', + name: 'Kaos "Enak Lho!"', + category: ShopCategory.merch, + price: 119000, + emoji: '๐Ÿ‘•', + tint: 0xFFE9E9E9, + unit: 'Katun combed 30s', + variantLabel: 'Ukuran', + variants: ['S', 'M', 'L', 'XL'], + description: + 'Kaos katun yang adem dipakai seharian, dengan sablon "Enak Lho!" di ' + 'bagian depan.', + soldCount: 48, + stock: 32, + addedAt: _daysAgo(35), + ), + ShopProduct( + id: 's13', + name: 'Tote Bag Kanvas Enaklo', + category: ShopCategory.merch, + price: 90000, + discountPrice: 76500, + emoji: '๐Ÿ›๏ธ', + tint: 0xFFEDE3CF, + unit: '38 ร— 42 cm', + variantLabel: 'Warna', + variants: ['Natural', 'Hitam'], + description: + 'Tote bag kanvas tebal dengan kantong dalam. Cukup untuk laptop 14 ' + 'inci.', + soldCount: 66, + stock: 3, + tag: ShopTag.newArrival, + addedAt: _daysAgo(8), + ), + ShopProduct( + id: 's14', + name: 'Hampers Rasa Nusantara', + category: ShopCategory.hampers, + price: 350000, + discountPrice: 299000, + emoji: '๐ŸŽ', + tint: 0xFFDCEBD6, + unit: 'Isi 5 produk', + description: + 'Sambal Woku Pedas, Kopi Tumulu House Blend, Nastar Keju Premium, ' + 'Keripik Kulit Ikan Pedas, dan kartu ucapan dalam kotak kayu.', + soldCount: 34, + stock: 12, + tag: ShopTag.newArrival, + addedAt: _daysAgo(6), + ), +];