From c36a4059e18d682d27aaf607c364fc2cfe5bf549 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 29 Aug 2025 15:40:45 +0700 Subject: [PATCH] feat: draw detail page --- lib/presentation/pages/draw/draw_page.dart | 427 +++++---- .../pages/draw_detail/draw_detail_page.dart | 905 ++++++++++++++++++ lib/presentation/router/app_router.dart | 1 + lib/presentation/router/app_router.gr.dart | 243 +++-- 4 files changed, 1270 insertions(+), 306 deletions(-) create mode 100644 lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart diff --git a/lib/presentation/pages/draw/draw_page.dart b/lib/presentation/pages/draw/draw_page.dart index 76cd0c5..ad1d364 100644 --- a/lib/presentation/pages/draw/draw_page.dart +++ b/lib/presentation/pages/draw/draw_page.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; +import '../../router/app_router.gr.dart'; // Models (simplified) class DrawEvent { @@ -220,242 +221,260 @@ class _DrawPageState extends State { _isUserEntered(draw.id); final timeRemaining = _getTimeRemaining(draw.drawDate); - return Container( - margin: EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: AppColor.surface, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.08), - blurRadius: 12, - offset: Offset(0, 4), - ), - ], - ), - child: Column( - children: [ - // Header with gradient background - Container( - height: 160, - width: double.infinity, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [draw.primaryColor, draw.primaryColor.withOpacity(0.8)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + return GestureDetector( + onTap: () => context.router.push(DrawDetailRoute(drawEvent: draw)), + child: Container( + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.08), + blurRadius: 12, + offset: Offset(0, 4), ), - child: Stack( - children: [ - // Background pattern (coins and gold bar) - Positioned( - right: 20, - top: 20, - child: Opacity( - opacity: 0.3, - child: Column( - children: [ - Row( - children: [ - _buildCoin(), - SizedBox(width: 8), - _buildCoin(), - ], - ), - SizedBox(height: 8), - _buildGoldBar(), - ], + ], + ), + child: Column( + children: [ + // Header with gradient background + Container( + height: 160, + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + draw.primaryColor, + draw.primaryColor.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + child: Stack( + children: [ + // Background pattern (coins and gold bar) + Positioned( + right: 20, + top: 20, + child: Opacity( + opacity: 0.3, + child: Column( + children: [ + Row( + children: [ + _buildCoin(), + SizedBox(width: 8), + _buildCoin(), + ], + ), + SizedBox(height: 8), + _buildGoldBar(), + ], + ), ), ), - ), - // Content - Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (draw.isActive) - Container( - padding: EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: AppColor.success, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - "AKTIF", - style: AppStyle.xs.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.bold, + // Content + Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (draw.isActive) + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.success, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + "AKTIF", + style: AppStyle.xs.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), ), ), + SizedBox(height: 8), + Text( + "MAKAN\nDAPAT", + style: AppStyle.h5.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + height: 0.9, + ), ), - SizedBox(height: 8), - Text( - "MAKAN\nDAPAT", - style: AppStyle.h5.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.bold, - height: 0.9, + SizedBox(height: 4), + Text( + draw.description, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite, + ), ), - ), - SizedBox(height: 4), - Text( - draw.description, - style: AppStyle.sm.copyWith(color: AppColor.textWhite), - ), - ], + ], + ), ), - ), - ], + ], + ), ), - ), - // Card content - Padding( - padding: EdgeInsets.all(16), - child: Column( - children: [ - // Prize info - Row( - children: [ - Text(draw.icon, style: AppStyle.h5), - SizedBox(width: 8), - Text( - draw.prize, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ], - ), - - SizedBox(height: 16), - - // Stats row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Text( - "-", - style: AppStyle.h5.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - Text( - "Peserta", - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - Column( - children: [ - Text( - "${draw.hadiah}", - style: AppStyle.h5.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - Text( - "Hadiah", - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - Column( - children: [ - Text( - "0", - style: AppStyle.h5.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.error, - ), - ), - Text( - "Voucher Anda", - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - ], - ), - - SizedBox(height: 16), - - // Timer - if (draw.isActive) + // Card content + Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + // Prize info Row( children: [ - Icon(Icons.access_time, color: AppColor.error, size: 16), - SizedBox(width: 4), + Text(draw.icon, style: AppStyle.h5), + SizedBox(width: 8), Text( - "Berakhir dalam:", - style: AppStyle.md.copyWith( + draw.prize, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, color: AppColor.textPrimary, ), ), - Spacer(), - Text( - timeRemaining, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.error, - ), - ), ], ), - SizedBox(height: 12), + SizedBox(height: 16), - // Spending requirement - GestureDetector( - onTap: () => _showSpendingInfo(draw), - child: Container( - padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), - decoration: BoxDecoration( - color: AppColor.backgroundLight, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColor.borderLight, width: 1), - ), - child: Row( - children: [ - Expanded( - child: Text( - "Belanja min. Rp ${_formatCurrency(draw.minSpending)} untuk berpartisipasi", + // Stats row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Text( + "-", + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + "Peserta", style: AppStyle.sm.copyWith( color: AppColor.textSecondary, ), ), - ), + ], + ), + Column( + children: [ + Text( + "${draw.hadiah}", + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + "Hadiah", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + Column( + children: [ + Text( + "0", + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.error, + ), + ), + Text( + "Voucher Anda", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ], + ), + + SizedBox(height: 16), + + // Timer + if (draw.isActive) + Row( + children: [ Icon( - Icons.chevron_right, - color: AppColor.textSecondary, + Icons.access_time, + color: AppColor.error, size: 16, ), + SizedBox(width: 4), + Text( + "Berakhir dalam:", + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + ), + ), + Spacer(), + Text( + timeRemaining, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.error, + ), + ), ], ), + + SizedBox(height: 12), + + // Spending requirement + GestureDetector( + onTap: () => _showSpendingInfo(draw), + child: Container( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColor.borderLight, + width: 1, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + "Belanja min. Rp ${_formatCurrency(draw.minSpending)} untuk berpartisipasi", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ), + Icon( + Icons.chevron_right, + color: AppColor.textSecondary, + size: 16, + ), + ], + ), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart b/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart new file mode 100644 index 0000000..e716a61 --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart @@ -0,0 +1,905 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../draw_page.dart'; + +// Prize model +class Prize { + final String id; + final String name; + final String value; + final String icon; + final int quantity; + final String description; + + Prize({ + required this.id, + required this.name, + required this.value, + required this.icon, + required this.quantity, + required this.description, + }); +} + +// Voucher model +class UserVoucher { + final String id; + final String drawId; + final String voucherNumber; + final DateTime createdDate; + final String status; // 'active', 'used', 'expired' + + UserVoucher({ + required this.id, + required this.drawId, + required this.voucherNumber, + required this.createdDate, + required this.status, + }); +} + +@RoutePage() +class DrawDetailPage extends StatefulWidget { + final DrawEvent drawEvent; + + const DrawDetailPage({super.key, required this.drawEvent}); + + @override + State createState() => _DrawDetailPageState(); +} + +class _DrawDetailPageState extends State + with TickerProviderStateMixin { + late TabController _tabController; + + // Sample data + final List prizes = [ + Prize( + id: "1", + name: "Emas 3 Gram", + value: "Rp 2.500.000", + icon: "👑", + quantity: 1, + description: + "Emas murni 24 karat seberat 3 gram dari toko emas terpercaya", + ), + Prize( + id: "2", + name: "Emas 1 Gram", + value: "Rp 850.000", + icon: "🥇", + quantity: 1, + description: + "Emas murni 24 karat seberat 1 gram dari toko emas terpercaya", + ), + ]; + + final List userVouchers = [ + UserVoucher( + id: "1", + drawId: "1", + voucherNumber: "ENK001234567", + createdDate: DateTime.now().subtract(Duration(hours: 2)), + status: 'active', + ), + ]; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 4, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + String _getTimeRemaining(DateTime targetDate) { + final now = DateTime.now(); + final difference = targetDate.difference(now); + + if (difference.isNegative) return "Sudah berakhir"; + + if (difference.inDays > 0) { + return "${difference.inDays} hari ${difference.inHours % 24} jam"; + } else if (difference.inHours > 0) { + return "${difference.inHours} jam ${difference.inMinutes % 60} menit"; + } else if (difference.inMinutes > 0) { + return "${difference.inMinutes} menit"; + } else { + return "Berakhir sekarang!"; + } + } + + String _formatCurrency(int amount) { + return amount.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: CustomScrollView( + slivers: [ + // App Bar with gradient + SliverAppBar( + expandedHeight: 200, + pinned: true, + backgroundColor: widget.drawEvent.primaryColor, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: AppColor.textWhite), + onPressed: () => Navigator.of(context).pop(), + ), + flexibleSpace: FlexibleSpaceBar( + background: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + widget.drawEvent.primaryColor, + widget.drawEvent.primaryColor.withOpacity(0.8), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Stack( + children: [ + // Background decoration + Positioned( + right: 20, + top: 60, + child: Opacity( + opacity: 0.2, + child: Column( + children: [ + Row( + children: [ + _buildCoin(), + SizedBox(width: 8), + _buildCoin(), + SizedBox(width: 8), + _buildCoin(), + ], + ), + SizedBox(height: 12), + _buildGoldBar(), + ], + ), + ), + ), + + // Content + Padding( + padding: EdgeInsets.only(left: 20, right: 20, top: 100), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.drawEvent.isActive) + Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColor.success, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + "UNDIAN AKTIF", + style: AppStyle.xs.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(height: 8), + Text( + widget.drawEvent.name, + style: AppStyle.h3.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + Text( + widget.drawEvent.description.split('\n').first, + style: AppStyle.md.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + + // Tab Bar + SliverPersistentHeader( + pinned: true, + delegate: _SliverTabBarDelegate( + TabBar( + controller: _tabController, + labelColor: AppColor.primary, + unselectedLabelColor: AppColor.textSecondary, + indicatorColor: AppColor.primary, + indicatorWeight: 3, + labelStyle: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + unselectedLabelStyle: AppStyle.md, + tabs: [ + Tab(text: "Info"), + Tab(text: "Hadiah"), + Tab(text: "Voucher"), + Tab(text: "S&K"), + ], + ), + ), + ), + + // Tab Content + SliverFillRemaining( + child: TabBarView( + controller: _tabController, + children: [ + _buildInfoTab(), + _buildPrizesTab(), + _buildVouchersTab(), + _buildTermsTab(), + ], + ), + ), + ], + ), + ); + } + + Widget _buildInfoTab() { + final timeRemaining = _getTimeRemaining(widget.drawEvent.drawDate); + + return SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoCard( + title: "Informasi Undian", + children: [ + _buildInfoRow("Nama Undian", widget.drawEvent.name), + _buildInfoRow("Deskripsi", widget.drawEvent.description), + _buildInfoRow( + "Total Hadiah", + "${widget.drawEvent.hadiah} hadiah", + ), + _buildInfoRow("Nilai Hadiah", widget.drawEvent.prizeValue), + _buildInfoRow( + "Status", + widget.drawEvent.isActive ? "Aktif" : "Selesai", + ), + ], + ), + + SizedBox(height: 16), + + _buildInfoCard( + title: "Waktu Undian", + children: [ + _buildInfoRow( + "Tanggal Pengundian", + _formatDateTime(widget.drawEvent.drawDate), + ), + _buildInfoRow("Waktu Tersisa", timeRemaining), + ], + ), + + SizedBox(height: 16), + + _buildInfoCard( + title: "Statistik", + children: [ + _buildInfoRow( + "Total Peserta", + "${widget.drawEvent.totalParticipants} orang", + ), + _buildInfoRow( + "Voucher Anda", + "${userVouchers.where((v) => v.drawId == widget.drawEvent.id).length}", + ), + _buildInfoRow( + "Minimum Belanja", + "Rp ${_formatCurrency(widget.drawEvent.minSpending)}", + ), + ], + ), + ], + ), + ); + } + + Widget _buildPrizesTab() { + return SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Daftar Hadiah", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 16), + + ...prizes.map((prize) => _buildPrizeCard(prize)).toList(), + + SizedBox(height: 16), + + _buildInfoCard( + title: "Ketentuan Hadiah", + children: [ + _buildBulletPoint( + "Hadiah akan diumumkan setelah pengundian selesai", + ), + _buildBulletPoint( + "Pemenang akan dihubungi melalui nomor telepon terdaftar", + ), + _buildBulletPoint("Hadiah harus diambil dalam waktu 30 hari"), + _buildBulletPoint( + "Hadiah tidak dapat dipindahtangankan atau ditukar uang", + ), + ], + ), + ], + ), + ); + } + + Widget _buildVouchersTab() { + final drawVouchers = userVouchers + .where((v) => v.drawId == widget.drawEvent.id) + .toList(); + + return SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Voucher Anda", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 8), + Text( + "Total: ${drawVouchers.length} voucher", + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + SizedBox(height: 16), + + if (drawVouchers.isEmpty) + _buildEmptyVoucherState() + else + ...drawVouchers + .map((voucher) => _buildVoucherCard(voucher)) + .toList(), + + SizedBox(height: 16), + + _buildInfoCard( + title: "Cara Mendapat Voucher", + children: [ + _buildBulletPoint( + "Lakukan pembelian minimum Rp ${_formatCurrency(widget.drawEvent.minSpending)}", + ), + _buildBulletPoint("Voucher otomatis akan masuk ke akun Anda"), + _buildBulletPoint( + "Semakin banyak voucher, semakin besar peluang menang", + ), + _buildBulletPoint("Voucher berlaku hingga pengundian selesai"), + ], + ), + ], + ), + ); + } + + Widget _buildTermsTab() { + return SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Syarat dan Ketentuan", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 16), + + _buildTermsSection( + title: "Syarat Partisipasi", + terms: [ + "Pengguna harus terdaftar sebagai member Enaklo", + "Melakukan pembelian minimum Rp ${_formatCurrency(widget.drawEvent.minSpending)}", + "Pembelian harus dilakukan sebelum waktu pengundian", + "Satu transaksi pembelian = satu voucher undian", + ], + ), + + _buildTermsSection( + title: "Ketentuan Pengundian", + terms: [ + "Pengundian dilakukan secara transparan dan fair", + "Pemenang ditentukan secara acak oleh sistem", + "Keputusan pengundian bersifat final dan tidak dapat diganggu gugat", + "Pengumuman pemenang akan dilakukan maksimal 3 hari setelah pengundian", + ], + ), + + _buildTermsSection( + title: "Ketentuan Hadiah", + terms: [ + "Hadiah harus diambil dalam waktu 30 hari setelah pengumuman", + "Hadiah yang tidak diambil dalam batas waktu dianggap hangus", + "Hadiah tidak dapat ditukar dengan uang tunai", + "Pajak hadiah (jika ada) menjadi tanggung jawab pemenang", + ], + ), + + _buildTermsSection( + title: "Ketentuan Lainnya", + terms: [ + "Enaklo berhak membatalkan undian jika terjadi kecurangan", + "Peserta bertanggung jawab atas kebenaran data yang diberikan", + "Enaklo tidak bertanggung jawab atas kerugian akibat kesalahan peserta", + "Syarat dan ketentuan dapat berubah sewaktu-waktu tanpa pemberitahuan", + ], + ), + ], + ), + ); + } + + Widget _buildInfoCard({ + required String title, + required List children, + }) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 12), + ...children, + ], + ), + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ), + Text( + ": ", + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + Expanded( + child: Text( + value, + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } + + Widget _buildPrizeCard(Prize prize) { + return Container( + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: widget.drawEvent.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(30), + ), + child: Center(child: Text(prize.icon, style: AppStyle.h4)), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + prize.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 4), + Text( + prize.value, + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 4), + Text( + prize.description, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + "${prize.quantity}x", + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildVoucherCard(UserVoucher voucher) { + Color statusColor; + String statusText; + + switch (voucher.status) { + case 'active': + statusColor = AppColor.success; + statusText = "Aktif"; + break; + case 'used': + statusColor = AppColor.textSecondary; + statusText = "Terpakai"; + break; + case 'expired': + statusColor = AppColor.error; + statusText = "Kadaluarsa"; + break; + default: + statusColor = AppColor.textSecondary; + statusText = "Tidak Diketahui"; + } + + return Container( + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: voucher.status == 'active' + ? AppColor.primary.withOpacity(0.3) + : AppColor.borderLight, + ), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Voucher Undian", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + statusText, + style: AppStyle.xs.copyWith( + color: statusColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + SizedBox(height: 8), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColor.borderLight), + ), + child: Center( + child: Text( + voucher.voucherNumber, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + letterSpacing: 1.2, + ), + ), + ), + ), + SizedBox(height: 8), + Text( + "Diperoleh: ${_formatDateTime(voucher.createdDate)}", + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ); + } + + Widget _buildEmptyVoucherState() { + return Container( + width: double.infinity, + padding: EdgeInsets.all(32), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight), + ), + child: Column( + children: [ + Icon(Icons.receipt_outlined, size: 64, color: AppColor.textLight), + SizedBox(height: 16), + Text( + "Belum Ada Voucher", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 8), + Text( + "Lakukan pembelian untuk mendapatkan voucher undian", + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: 16), + ElevatedButton( + onPressed: () { + // Navigate to shop or show purchase dialog + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.textWhite, + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text( + "Belanja Sekarang", + style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + ), + ), + ], + ), + ); + } + + Widget _buildTermsSection({ + required String title, + required List terms, + }) { + return Container( + margin: EdgeInsets.only(bottom: 24), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 12), + ...terms.map((term) => _buildBulletPoint(term)).toList(), + ], + ), + ), + ); + } + + Widget _buildBulletPoint(String text) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 6), + width: 4, + height: 4, + decoration: BoxDecoration( + color: AppColor.primary, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 12), + Expanded( + child: Text( + text, + style: AppStyle.md.copyWith(color: AppColor.textPrimary), + ), + ), + ], + ), + ); + } + + Widget _buildCoin() { + return Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: AppColor.warning, + shape: BoxShape.circle, + border: Border.all(color: Colors.yellow, width: 1), + ), + child: Center( + child: Text( + "₹", + style: AppStyle.sm.copyWith( + color: Colors.orange[800], + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + Widget _buildGoldBar() { + return Container( + width: 40, + height: 20, + decoration: BoxDecoration( + color: AppColor.warning, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.yellow, width: 1), + ), + child: Center( + child: Text( + "GOLD", + style: AppStyle.xs.copyWith( + color: Colors.orange[800], + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + String _formatDateTime(DateTime date) { + return "${date.day}/${date.month}/${date.year} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}"; + } +} + +// Custom SliverTabBarDelegate +class _SliverTabBarDelegate extends SliverPersistentHeaderDelegate { + final TabBar _tabBar; + + _SliverTabBarDelegate(this._tabBar); + + @override + double get minExtent => _tabBar.preferredSize.height; + @override + double get maxExtent => _tabBar.preferredSize.height; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Container(color: AppColor.surface, child: _tabBar); + } + + @override + bool shouldRebuild(_SliverTabBarDelegate oldDelegate) { + return false; + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index a05794f..e1d1cf2 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -37,5 +37,6 @@ class AppRouter extends RootStackRouter { // Draw AutoRoute(page: DrawRoute.page), + AutoRoute(page: DrawDetailRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 715bf3f..ade518b 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -9,167 +9,206 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i16; -import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i3; -import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i8; -import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i9; +import 'package:auto_route/auto_route.dart' as _i17; +import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i4; +import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i9; +import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i10; import 'package:enaklo/presentation/pages/auth/register/register_page.dart' - as _i12; -import 'package:enaklo/presentation/pages/draw/draw_page.dart' as _i1; -import 'package:enaklo/presentation/pages/main/main_page.dart' as _i4; + as _i13; +import 'package:enaklo/presentation/pages/draw/draw_page.dart' as _i2; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart' + as _i1; +import 'package:enaklo/presentation/pages/main/main_page.dart' as _i5; import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart' - as _i2; + as _i3; import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' - as _i7; + as _i8; import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' - as _i11; + as _i12; import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' - as _i15; -import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i5; + as _i16; +import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i6; import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart' - as _i6; + as _i7; import 'package:enaklo/presentation/pages/reward/pages/product_redeem/product_redeem_page.dart' - as _i10; -import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i13; -import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i14; -import 'package:flutter/material.dart' as _i17; + as _i11; +import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i14; +import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i15; +import 'package:flutter/material.dart' as _i18; /// generated route for -/// [_i1.DrawPage] -class DrawRoute extends _i16.PageRouteInfo { - const DrawRoute({List<_i16.PageRouteInfo>? children}) +/// [_i1.DrawDetailPage] +class DrawDetailRoute extends _i17.PageRouteInfo { + DrawDetailRoute({ + _i18.Key? key, + required _i2.DrawEvent drawEvent, + List<_i17.PageRouteInfo>? children, + }) : super( + DrawDetailRoute.name, + args: DrawDetailRouteArgs(key: key, drawEvent: drawEvent), + initialChildren: children, + ); + + static const String name = 'DrawDetailRoute'; + + static _i17.PageInfo page = _i17.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i1.DrawDetailPage(key: args.key, drawEvent: args.drawEvent); + }, + ); +} + +class DrawDetailRouteArgs { + const DrawDetailRouteArgs({this.key, required this.drawEvent}); + + final _i18.Key? key; + + final _i2.DrawEvent drawEvent; + + @override + String toString() { + return 'DrawDetailRouteArgs{key: $key, drawEvent: $drawEvent}'; + } +} + +/// generated route for +/// [_i2.DrawPage] +class DrawRoute extends _i17.PageRouteInfo { + const DrawRoute({List<_i17.PageRouteInfo>? children}) : super(DrawRoute.name, initialChildren: children); static const String name = 'DrawRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i1.DrawPage(); + return const _i2.DrawPage(); }, ); } /// generated route for -/// [_i2.HomePage] -class HomeRoute extends _i16.PageRouteInfo { - const HomeRoute({List<_i16.PageRouteInfo>? children}) +/// [_i3.HomePage] +class HomeRoute extends _i17.PageRouteInfo { + const HomeRoute({List<_i17.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i2.HomePage(); + return const _i3.HomePage(); }, ); } /// generated route for -/// [_i3.LoginPage] -class LoginRoute extends _i16.PageRouteInfo { - const LoginRoute({List<_i16.PageRouteInfo>? children}) +/// [_i4.LoginPage] +class LoginRoute extends _i17.PageRouteInfo { + const LoginRoute({List<_i17.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i3.LoginPage(); + return const _i4.LoginPage(); }, ); } /// generated route for -/// [_i4.MainPage] -class MainRoute extends _i16.PageRouteInfo { - const MainRoute({List<_i16.PageRouteInfo>? children}) +/// [_i5.MainPage] +class MainRoute extends _i17.PageRouteInfo { + const MainRoute({List<_i17.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i4.MainPage(); + return const _i5.MainPage(); }, ); } /// generated route for -/// [_i5.MerchantPage] -class MerchantRoute extends _i16.PageRouteInfo { - const MerchantRoute({List<_i16.PageRouteInfo>? children}) +/// [_i6.MerchantPage] +class MerchantRoute extends _i17.PageRouteInfo { + const MerchantRoute({List<_i17.PageRouteInfo>? children}) : super(MerchantRoute.name, initialChildren: children); static const String name = 'MerchantRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i5.MerchantPage(); + return const _i6.MerchantPage(); }, ); } /// generated route for -/// [_i6.OnboardingPage] -class OnboardingRoute extends _i16.PageRouteInfo { - const OnboardingRoute({List<_i16.PageRouteInfo>? children}) +/// [_i7.OnboardingPage] +class OnboardingRoute extends _i17.PageRouteInfo { + const OnboardingRoute({List<_i17.PageRouteInfo>? children}) : super(OnboardingRoute.name, initialChildren: children); static const String name = 'OnboardingRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i6.OnboardingPage(); + return const _i7.OnboardingPage(); }, ); } /// generated route for -/// [_i7.OrderPage] -class OrderRoute extends _i16.PageRouteInfo { - const OrderRoute({List<_i16.PageRouteInfo>? children}) +/// [_i8.OrderPage] +class OrderRoute extends _i17.PageRouteInfo { + const OrderRoute({List<_i17.PageRouteInfo>? children}) : super(OrderRoute.name, initialChildren: children); static const String name = 'OrderRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i7.OrderPage(); + return const _i8.OrderPage(); }, ); } /// generated route for -/// [_i8.OtpPage] -class OtpRoute extends _i16.PageRouteInfo { - const OtpRoute({List<_i16.PageRouteInfo>? children}) +/// [_i9.OtpPage] +class OtpRoute extends _i17.PageRouteInfo { + const OtpRoute({List<_i17.PageRouteInfo>? children}) : super(OtpRoute.name, initialChildren: children); static const String name = 'OtpRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i8.OtpPage(); + return const _i9.OtpPage(); }, ); } /// generated route for -/// [_i9.PinPage] -class PinRoute extends _i16.PageRouteInfo { +/// [_i10.PinPage] +class PinRoute extends _i17.PageRouteInfo { PinRoute({ - _i17.Key? key, + _i18.Key? key, bool isCreatePin = true, String? title, - List<_i16.PageRouteInfo>? children, + List<_i17.PageRouteInfo>? children, }) : super( PinRoute.name, args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title), @@ -178,13 +217,13 @@ class PinRoute extends _i16.PageRouteInfo { static const String name = 'PinRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const PinRouteArgs(), ); - return _i9.PinPage( + return _i10.PinPage( key: args.key, isCreatePin: args.isCreatePin, title: args.title, @@ -196,7 +235,7 @@ class PinRoute extends _i16.PageRouteInfo { class PinRouteArgs { const PinRouteArgs({this.key, this.isCreatePin = true, this.title}); - final _i17.Key? key; + final _i18.Key? key; final bool isCreatePin; @@ -209,14 +248,14 @@ class PinRouteArgs { } /// generated route for -/// [_i10.ProductRedeemPage] -class ProductRedeemRoute extends _i16.PageRouteInfo { +/// [_i11.ProductRedeemPage] +class ProductRedeemRoute extends _i17.PageRouteInfo { ProductRedeemRoute({ - _i17.Key? key, - required _i13.Product product, - required _i13.Merchant merchant, - required _i13.PointCard pointCard, - List<_i16.PageRouteInfo>? children, + _i18.Key? key, + required _i14.Product product, + required _i14.Merchant merchant, + required _i14.PointCard pointCard, + List<_i17.PageRouteInfo>? children, }) : super( ProductRedeemRoute.name, args: ProductRedeemRouteArgs( @@ -230,11 +269,11 @@ class ProductRedeemRoute extends _i16.PageRouteInfo { static const String name = 'ProductRedeemRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i10.ProductRedeemPage( + return _i11.ProductRedeemPage( key: args.key, product: args.product, merchant: args.merchant, @@ -252,13 +291,13 @@ class ProductRedeemRouteArgs { required this.pointCard, }); - final _i17.Key? key; + final _i18.Key? key; - final _i13.Product product; + final _i14.Product product; - final _i13.Merchant merchant; + final _i14.Merchant merchant; - final _i13.PointCard pointCard; + final _i14.PointCard pointCard; @override String toString() { @@ -267,81 +306,81 @@ class ProductRedeemRouteArgs { } /// generated route for -/// [_i11.ProfilePage] -class ProfileRoute extends _i16.PageRouteInfo { - const ProfileRoute({List<_i16.PageRouteInfo>? children}) +/// [_i12.ProfilePage] +class ProfileRoute extends _i17.PageRouteInfo { + const ProfileRoute({List<_i17.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i11.ProfilePage(); + return const _i12.ProfilePage(); }, ); } /// generated route for -/// [_i12.RegisterPage] -class RegisterRoute extends _i16.PageRouteInfo { - const RegisterRoute({List<_i16.PageRouteInfo>? children}) +/// [_i13.RegisterPage] +class RegisterRoute extends _i17.PageRouteInfo { + const RegisterRoute({List<_i17.PageRouteInfo>? children}) : super(RegisterRoute.name, initialChildren: children); static const String name = 'RegisterRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i12.RegisterPage(); + return const _i13.RegisterPage(); }, ); } /// generated route for -/// [_i13.RewardPage] -class RewardRoute extends _i16.PageRouteInfo { - const RewardRoute({List<_i16.PageRouteInfo>? children}) +/// [_i14.RewardPage] +class RewardRoute extends _i17.PageRouteInfo { + const RewardRoute({List<_i17.PageRouteInfo>? children}) : super(RewardRoute.name, initialChildren: children); static const String name = 'RewardRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i13.RewardPage(); + return const _i14.RewardPage(); }, ); } /// generated route for -/// [_i14.SplashPage] -class SplashRoute extends _i16.PageRouteInfo { - const SplashRoute({List<_i16.PageRouteInfo>? children}) +/// [_i15.SplashPage] +class SplashRoute extends _i17.PageRouteInfo { + const SplashRoute({List<_i17.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i14.SplashPage(); + return const _i15.SplashPage(); }, ); } /// generated route for -/// [_i15.VoucherPage] -class VoucherRoute extends _i16.PageRouteInfo { - const VoucherRoute({List<_i16.PageRouteInfo>? children}) +/// [_i16.VoucherPage] +class VoucherRoute extends _i17.PageRouteInfo { + const VoucherRoute({List<_i17.PageRouteInfo>? children}) : super(VoucherRoute.name, initialChildren: children); static const String name = 'VoucherRoute'; - static _i16.PageInfo page = _i16.PageInfo( + static _i17.PageInfo page = _i17.PageInfo( name, builder: (data) { - return const _i15.VoucherPage(); + return const _i16.VoucherPage(); }, ); }