Compare commits
No commits in common. "06cc7e378143d0cb6169d7cc3c7f4fb023135c84" and "c790d00341297f47d2fddd7f42e1069492356a04" have entirely different histories.
06cc7e3781
...
c790d00341
@ -16,6 +16,19 @@ class ThemeApp {
|
||||
fontFamily: FontFamily.quicksand,
|
||||
primaryColor: AppColor.primary,
|
||||
scaffoldBackgroundColor: AppColor.white,
|
||||
textTheme: TextTheme(
|
||||
bodySmall: AppStyle.xs,
|
||||
bodyMedium: AppStyle.md,
|
||||
bodyLarge: AppStyle.lg,
|
||||
labelSmall: AppStyle.sm,
|
||||
labelLarge: AppStyle.xl,
|
||||
headlineSmall: AppStyle.h6,
|
||||
headlineMedium: AppStyle.h5,
|
||||
headlineLarge: AppStyle.h4,
|
||||
displaySmall: AppStyle.h3,
|
||||
displayMedium: AppStyle.h2,
|
||||
displayLarge: AppStyle.h1,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: AppColor.white,
|
||||
foregroundColor: AppColor.textPrimary,
|
||||
|
||||
@ -107,6 +107,10 @@ class _DrawPageState extends State<DrawPage> {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool _isUserEntered(String drawId) {
|
||||
return userEntries.any((entry) => entry.drawId == drawId);
|
||||
}
|
||||
|
||||
String _getTimeRemaining(DateTime targetDate) {
|
||||
final now = DateTime.now();
|
||||
final difference = targetDate.difference(now);
|
||||
@ -214,82 +218,389 @@ class _DrawPageState extends State<DrawPage> {
|
||||
}
|
||||
|
||||
Widget _buildSimpleDrawCard(DrawEvent draw) {
|
||||
_isUserEntered(draw.id);
|
||||
final timeRemaining = _getTimeRemaining(draw.drawDate);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.router.push(DrawDetailRoute(drawEvent: draw)),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
padding: EdgeInsets.all(12),
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [draw.primaryColor, draw.primaryColor.withOpacity(0.8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: AppColor.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.08),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
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)),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 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)
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
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: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Name
|
||||
Text(
|
||||
draw.name,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
// Description
|
||||
Text(
|
||||
draw.description,
|
||||
child: Text(
|
||||
"Belanja min. Rp ${_formatCurrency(draw.minSpending)} untuk berpartisipasi",
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.9),
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
// Date
|
||||
Text(
|
||||
draw.isActive ? "Berakhir: $timeRemaining" : "Selesai",
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.8),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: AppColor.textSecondary,
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Price
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(draw.icon, style: AppStyle.lg),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
draw.prize,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoin() {
|
||||
return Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.yellow, width: 1),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"₹",
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: Colors.orange[800],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGoldBar() {
|
||||
return Container(
|
||||
width: 30,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
border: Border.all(color: Colors.yellow, width: 1),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"GOLD",
|
||||
style: TextStyle(
|
||||
color: Colors.orange[800],
|
||||
fontSize: 6,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatCurrency(int amount) {
|
||||
return amount.toString().replaceAllMapped(
|
||||
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]}.',
|
||||
);
|
||||
}
|
||||
|
||||
void _showSpendingInfo(DrawEvent draw) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: AppColor.surface,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
title: Text(
|
||||
"Syarat Partisipasi",
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Untuk mengikuti undian ${draw.name}, Anda perlu:",
|
||||
style: AppStyle.md.copyWith(color: AppColor.textPrimary),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.shopping_cart, color: draw.primaryColor, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Belanja minimum Rp ${_formatCurrency(draw.minSpending)}",
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.schedule, color: draw.primaryColor, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Sebelum ${_formatDateTime(draw.drawDate)}",
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
"Mengerti",
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime date) {
|
||||
return "${date.day}/${date.month}/${date.year} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}";
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ class _HomePageState extends State<HomePage> {
|
||||
_buildHeaderSection(),
|
||||
const SizedBox(height: 70),
|
||||
HomeFeatureSection(),
|
||||
HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())),
|
||||
HomeLotteryBanner(),
|
||||
HomePopularMerchantSection(),
|
||||
],
|
||||
),
|
||||
|
||||
@ -8,7 +8,7 @@ class HomeLotteryBanner extends StatefulWidget {
|
||||
this.title = "🎰 UNDIAN BERHADIAH",
|
||||
this.subtitle = "Kumpulkan voucher untuk menang hadiah menarik!",
|
||||
this.showAnimation = true,
|
||||
this.actionText = "IKUTI SEKARANG",
|
||||
this.actionText = "MAIN SEKARANG",
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@ -144,6 +144,7 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: _buildAppBar(),
|
||||
body: Column(
|
||||
children: [
|
||||
@ -160,7 +161,6 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
return AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text('Pesanan'),
|
||||
actions: [
|
||||
IconButton(
|
||||
|
||||
@ -15,32 +15,64 @@ class OrderCard extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: () => context.router.push(OrderDetailRoute(order: order)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 0),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColor.border.withOpacity(0.3),
|
||||
width: 1,
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.06),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
_buildContent(),
|
||||
const SizedBox(height: 16),
|
||||
_buildFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Left side - Order info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Order ID
|
||||
Text(
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
order.id,
|
||||
style: AppStyle.md.copyWith(
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Order Date
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
DateFormat('dd MMM yyyy • HH:mm').format(order.orderDate),
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
@ -48,47 +80,206 @@ class OrderCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildStatusChip(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Right side - Status and Total
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
// Status
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
Widget _buildStatusChip() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor().withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _getStatusColor().withOpacity(0.2),
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: _getStatusColor().withOpacity(0.2), width: 1),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(_getStatusIcon(), size: 12, color: _getStatusColor()),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
_getStatusText(),
|
||||
style: AppStyle.xs.copyWith(
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: _getStatusColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.restaurant_menu_outlined,
|
||||
size: 16,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'${order.items.length} item pesanan',
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...order.items
|
||||
.take(3)
|
||||
.map(
|
||||
(item) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${item.quantity}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Rp ${_formatCurrency(item.price * item.quantity)}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (order.items.length > 3) ...[
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'+${order.items.length - 3} item lainnya',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (order.notes != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColor.warning.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.sticky_note_2_outlined,
|
||||
size: 14,
|
||||
color: AppColor.warning,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.notes!,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter() {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: AppColor.border.withOpacity(0.3),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
order.address != null
|
||||
? Icons.location_on_outlined
|
||||
: Icons.store_outlined,
|
||||
size: 16,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.address ?? 'Ambil di tempat',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'Total',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
// Total Amount
|
||||
Text(
|
||||
'Rp ${_formatCurrency(order.totalAmount)}',
|
||||
style: AppStyle.md.copyWith(
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -118,6 +309,19 @@ class OrderCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getStatusIcon() {
|
||||
switch (order.status) {
|
||||
case OrderStatus.pending:
|
||||
return Icons.schedule;
|
||||
case OrderStatus.processing:
|
||||
return Icons.hourglass_empty;
|
||||
case OrderStatus.completed:
|
||||
return Icons.check_circle;
|
||||
case OrderStatus.cancelled:
|
||||
return Icons.cancel;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatCurrency(double amount) {
|
||||
final formatter = NumberFormat('#,###');
|
||||
return formatter.format(amount);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
|
||||
@ -11,7 +12,7 @@ class ProfilePage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: AppBar(title: Text('Profil'), automaticallyImplyLeading: false),
|
||||
appBar: AppBar(title: Text('Akun')),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
@ -164,6 +165,61 @@ class ProfilePage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Share the Sip Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.backgroundLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Share Icon
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.success.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.share,
|
||||
color: AppColor.success,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Share Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Share the Sip',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Bagikan kode referral, dapatkan hadiah',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.textSecondary,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -224,6 +280,52 @@ class ProfilePage extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Social Media Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Media Sosial',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildSocialButton(
|
||||
icon: Icons.camera_alt,
|
||||
color: Colors.purple,
|
||||
onTap: () => _launchSocialMedia('instagram'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.facebook,
|
||||
color: Colors.blue,
|
||||
onTap: () => _launchSocialMedia('facebook'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.play_arrow,
|
||||
color: Colors.red,
|
||||
onTap: () => _launchSocialMedia('youtube'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.close,
|
||||
color: Colors.black,
|
||||
onTap: () => _launchSocialMedia('twitter'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Customer Service Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
@ -365,12 +467,49 @@ class ProfilePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSocialButton({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
child: Icon(icon, color: AppColor.white, size: 24),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _launchSocialMedia(String platform) async {
|
||||
String url = '';
|
||||
switch (platform) {
|
||||
case 'instagram':
|
||||
url = 'https://instagram.com/';
|
||||
break;
|
||||
case 'facebook':
|
||||
url = 'https://facebook.com/';
|
||||
break;
|
||||
case 'youtube':
|
||||
url = 'https://youtube.com/';
|
||||
break;
|
||||
case 'twitter':
|
||||
url = 'https://twitter.com/';
|
||||
break;
|
||||
}
|
||||
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
}
|
||||
|
||||
void _showLogoutDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: AppColor.white,
|
||||
title: Text(
|
||||
'Logout',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
||||
|
||||
@ -19,14 +19,11 @@ class _MerchantPageState extends State<MerchantPage> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final List<MerchantModel> _allMerchants = _generateMockMerchants();
|
||||
List<MerchantModel> _filteredMerchants = [];
|
||||
String? _selectedCategory;
|
||||
late List<String> _categories;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_filteredMerchants = _allMerchants;
|
||||
_categories = _getAllCategories();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -35,29 +32,12 @@ class _MerchantPageState extends State<MerchantPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<String> _getAllCategories() {
|
||||
final categories = _allMerchants
|
||||
.map((merchant) => merchant.category)
|
||||
.toSet()
|
||||
.toList();
|
||||
categories.sort();
|
||||
return categories;
|
||||
}
|
||||
|
||||
void _filterMerchants(String query) {
|
||||
setState(() {
|
||||
var merchants = _allMerchants;
|
||||
|
||||
// Filter by category first
|
||||
if (_selectedCategory != null) {
|
||||
merchants = merchants
|
||||
.where((merchant) => merchant.category == _selectedCategory)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Then filter by search query
|
||||
if (query.isNotEmpty) {
|
||||
merchants = merchants
|
||||
if (query.isEmpty) {
|
||||
_filteredMerchants = _allMerchants;
|
||||
} else {
|
||||
_filteredMerchants = _allMerchants
|
||||
.where(
|
||||
(merchant) =>
|
||||
merchant.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||
@ -65,98 +45,26 @@ class _MerchantPageState extends State<MerchantPage> {
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
_filteredMerchants = merchants;
|
||||
});
|
||||
}
|
||||
|
||||
void _onCategorySelected(String? category) {
|
||||
setState(() {
|
||||
_selectedCategory = category;
|
||||
});
|
||||
_filterMerchants(_searchController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: AppBar(
|
||||
title: const Text('Merchants'),
|
||||
title: Text('Merchants'),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(
|
||||
130,
|
||||
), // Increased height for filter chips
|
||||
child: Column(
|
||||
children: [
|
||||
// Search Field
|
||||
SearchTextField(
|
||||
preferredSize: const Size.fromHeight(70),
|
||||
child: SearchTextField(
|
||||
hintText: 'Search merchants...',
|
||||
prefixIcon: Icons.search,
|
||||
controller: _searchController,
|
||||
// onChanged: _filterMerchants,
|
||||
onClear: () {
|
||||
_searchController.clear();
|
||||
_filterMerchants('');
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Category Filter Chips
|
||||
// Category Filter Chips - Updated with AppColor and opacity 1
|
||||
Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
// "All" filter chip
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: FilterChip(
|
||||
label: const Text('All'),
|
||||
selected: _selectedCategory == null,
|
||||
onSelected: (selected) {
|
||||
_onCategorySelected(null);
|
||||
},
|
||||
selectedColor: AppColor.primary,
|
||||
backgroundColor: AppColor.white,
|
||||
checkmarkColor: AppColor.white,
|
||||
labelStyle: TextStyle(
|
||||
color: _selectedCategory == null
|
||||
? AppColor.white
|
||||
: AppColor.primary,
|
||||
),
|
||||
side: BorderSide(color: AppColor.primary, width: 1),
|
||||
),
|
||||
),
|
||||
// Category filter chips
|
||||
..._categories.map(
|
||||
(category) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: FilterChip(
|
||||
label: Text(category),
|
||||
selected: _selectedCategory == category,
|
||||
onSelected: (selected) {
|
||||
_onCategorySelected(selected ? category : null);
|
||||
},
|
||||
selectedColor: AppColor.primary,
|
||||
backgroundColor: AppColor.white,
|
||||
checkmarkColor: AppColor.white,
|
||||
labelStyle: TextStyle(
|
||||
color: _selectedCategory == category
|
||||
? AppColor.white
|
||||
: AppColor.primary,
|
||||
),
|
||||
side: BorderSide(color: AppColor.primary, width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: _filteredMerchants.isEmpty
|
||||
|
||||
@ -47,7 +47,19 @@ class _OrderDetailPageState extends State<OrderDetailPage> {
|
||||
|
||||
PreferredSizeWidget _buildAppBar() {
|
||||
return AppBar(
|
||||
title: Text('Detail Pesanan'),
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.white,
|
||||
leading: IconButton(
|
||||
onPressed: () => context.router.back(),
|
||||
icon: const Icon(Icons.arrow_back, color: AppColor.textPrimary),
|
||||
),
|
||||
title: Text(
|
||||
'Detail Pesanan',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _shareOrder,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user