Compare commits
No commits in common. "4c12244d3c55b0d101a6ebf17a390c6ff5619638" and "82e1f93cce664226e0178ce367299ad8c653e552" have entirely different histories.
4c12244d3c
...
82e1f93cce
Binary file not shown.
|
Before Width: | Height: | Size: 332 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 367 KiB |
@ -1,9 +1,5 @@
|
|||||||
import '../../sample/sample_data.dart';
|
|
||||||
|
|
||||||
class AppConstant {
|
class AppConstant {
|
||||||
static const String appName = "Enaklo";
|
static const String appName = "Enaklo";
|
||||||
static const String coinName = "EnakCoin";
|
static const String coinName = "EnakCoin";
|
||||||
static const String poinName = "EnakPoin";
|
static const String poinName = "EnakPoin";
|
||||||
}
|
}
|
||||||
|
|
||||||
MerchantModel merchant = merchants.first;
|
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
import '../../presentation/components/assets/assets.gen.dart';
|
|
||||||
|
|
||||||
class Service {
|
|
||||||
Service({
|
|
||||||
required this.name,
|
|
||||||
required this.description,
|
|
||||||
required this.imagePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String name;
|
|
||||||
final String imagePath;
|
|
||||||
final String description;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Service> services = [
|
|
||||||
Service(
|
|
||||||
name: 'Dine In',
|
|
||||||
description: 'Makan langsung di tempat',
|
|
||||||
imagePath: Assets.icons.dineIn.path,
|
|
||||||
),
|
|
||||||
Service(
|
|
||||||
name: 'Take Away',
|
|
||||||
description: 'Pesan dan bawa pulang',
|
|
||||||
imagePath: Assets.icons.dineIn.path,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
part of 'extension.dart';
|
|
||||||
|
|
||||||
extension DoubleExt on double {
|
|
||||||
String get currencyFormatRpV2 => NumberFormat.currency(
|
|
||||||
locale: 'id',
|
|
||||||
symbol: 'Rp ',
|
|
||||||
decimalDigits: 0,
|
|
||||||
).format(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StringX on String {
|
|
||||||
String get currencyFormatRp {
|
|
||||||
final parsedValue = int.tryParse(this) ?? 0;
|
|
||||||
return NumberFormat.currency(
|
|
||||||
locale: 'id',
|
|
||||||
symbol: 'Rp ',
|
|
||||||
decimalDigits: 0,
|
|
||||||
).format(parsedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IntegerExt on int {
|
|
||||||
String get currencyFormatRp => NumberFormat.currency(
|
|
||||||
locale: 'id',
|
|
||||||
symbol: 'Rp ',
|
|
||||||
decimalDigits: 0,
|
|
||||||
).format(this);
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ import 'package:intl/intl.dart';
|
|||||||
import '../../domain/auth/auth.dart';
|
import '../../domain/auth/auth.dart';
|
||||||
|
|
||||||
part 'date_extension.dart';
|
part 'date_extension.dart';
|
||||||
part 'currency_extension.dart';
|
|
||||||
|
|
||||||
extension StringExt on String {
|
extension StringExt on String {
|
||||||
CheckPhoneStatus toCheckPhoneStatus() {
|
CheckPhoneStatus toCheckPhoneStatus() {
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
part of 'theme.dart';
|
part of 'theme.dart';
|
||||||
|
|
||||||
class AppValue {
|
class AppValue {}
|
||||||
static const double padding = 16;
|
|
||||||
static const double margin = 16;
|
|
||||||
static const double borderRadius = 12;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class ThemeApp {
|
|||||||
foregroundColor: AppColor.textPrimary,
|
foregroundColor: AppColor.textPrimary,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
titleTextStyle: AppStyle.xl.copyWith(
|
titleTextStyle: AppStyle.xl.copyWith(
|
||||||
color: AppColor.textPrimary,
|
color: AppColor.primary,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
@ -55,18 +55,7 @@ class ThemeApp {
|
|||||||
backgroundColor: AppColor.primary,
|
backgroundColor: AppColor.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||||
borderRadius: BorderRadius.circular(AppValue.borderRadius),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: AppColor.primary,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: BorderSide(color: AppColor.border),
|
|
||||||
borderRadius: BorderRadiusGeometry.circular(AppValue.borderRadius),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
|||||||
@ -43,20 +43,6 @@ class $AssetsAudioGen {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class $AssetsIconsGen {
|
|
||||||
const $AssetsIconsGen();
|
|
||||||
|
|
||||||
/// File path: assets/icons/dine_in.png
|
|
||||||
AssetGenImage get dineIn => const AssetGenImage('assets/icons/dine_in.png');
|
|
||||||
|
|
||||||
/// File path: assets/icons/takeaway.png
|
|
||||||
AssetGenImage get takeaway =>
|
|
||||||
const AssetGenImage('assets/icons/takeaway.png');
|
|
||||||
|
|
||||||
/// List of all assets
|
|
||||||
List<AssetGenImage> get values => [dineIn, takeaway];
|
|
||||||
}
|
|
||||||
|
|
||||||
class $AssetsImagesGen {
|
class $AssetsImagesGen {
|
||||||
const $AssetsImagesGen();
|
const $AssetsImagesGen();
|
||||||
|
|
||||||
@ -111,7 +97,6 @@ class Assets {
|
|||||||
const Assets._();
|
const Assets._();
|
||||||
|
|
||||||
static const $AssetsAudioGen audio = $AssetsAudioGen();
|
static const $AssetsAudioGen audio = $AssetsAudioGen();
|
||||||
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
|
||||||
static const $AssetsImagesGen images = $AssetsImagesGen();
|
static const $AssetsImagesGen images = $AssetsImagesGen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class DashedDivider extends StatelessWidget {
|
|
||||||
final double height;
|
|
||||||
final double dashWidth;
|
|
||||||
final double dashSpacing;
|
|
||||||
final Color color;
|
|
||||||
|
|
||||||
const DashedDivider({
|
|
||||||
super.key,
|
|
||||||
this.height = 1,
|
|
||||||
this.dashWidth = 5,
|
|
||||||
this.dashSpacing = 3,
|
|
||||||
this.color = Colors.grey,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: height,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
final boxWidth = constraints.constrainWidth();
|
|
||||||
final dashCount = (boxWidth / (dashWidth + dashSpacing)).floor();
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: List.generate(dashCount, (_) {
|
|
||||||
return SizedBox(
|
|
||||||
width: dashWidth,
|
|
||||||
height: height,
|
|
||||||
child: DecoratedBox(decoration: BoxDecoration(color: color)),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,5 +4,3 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
|
|
||||||
part 'elevated_button.dart';
|
part 'elevated_button.dart';
|
||||||
part 'outline_button.dart';
|
|
||||||
part 'qty_button.dart';
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class AppElevatedButton extends StatelessWidget {
|
|||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.width = double.infinity,
|
this.width = double.infinity,
|
||||||
this.height = 44.0,
|
this.height = 48.0,
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class AppElevatedButton extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
title,
|
title,
|
||||||
style: AppStyle.md.copyWith(
|
style: AppStyle.lg.copyWith(
|
||||||
color: AppColor.white,
|
color: AppColor.white,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
part of 'button.dart';
|
|
||||||
|
|
||||||
class AppOutlineButton extends StatelessWidget {
|
|
||||||
const AppOutlineButton({
|
|
||||||
super.key,
|
|
||||||
required this.onPressed,
|
|
||||||
required this.title,
|
|
||||||
this.width = double.infinity,
|
|
||||||
this.height = 44.0,
|
|
||||||
this.isLoading = false,
|
|
||||||
this.borderColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Function()? onPressed;
|
|
||||||
final String title;
|
|
||||||
final double width;
|
|
||||||
final double height;
|
|
||||||
final bool isLoading;
|
|
||||||
final Color? borderColor;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: onPressed,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
side: BorderSide(color: borderColor ?? AppColor.primary),
|
|
||||||
),
|
|
||||||
child: isLoading
|
|
||||||
? Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SpinKitFadingCircle(color: AppColor.white, size: 24),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'Loading',
|
|
||||||
style: AppStyle.lg.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
title,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.primary,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
part of 'button.dart';
|
|
||||||
|
|
||||||
class QtyButton extends StatelessWidget {
|
|
||||||
const QtyButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
InkWell(
|
|
||||||
child: Container(
|
|
||||||
height: 35,
|
|
||||||
width: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
border: Border.all(width: 1, color: AppColor.border),
|
|
||||||
),
|
|
||||||
child: Icon(Icons.remove),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Text('1', style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold)),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
InkWell(
|
|
||||||
child: Container(
|
|
||||||
height: 35,
|
|
||||||
width: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
border: Border.all(width: 1, color: AppColor.border),
|
|
||||||
),
|
|
||||||
child: Icon(Icons.add),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/extension/extension.dart';
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
import '../../../sample/product_sample_data.dart';
|
|
||||||
|
|
||||||
class ProductCard extends StatelessWidget {
|
|
||||||
final Product product;
|
|
||||||
final Function()? onTap;
|
|
||||||
const ProductCard({super.key, required this.product, this.onTap});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.black.withOpacity(0.06),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Product image
|
|
||||||
Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.backgroundLight,
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
top: Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.fastfood,
|
|
||||||
size: 40,
|
|
||||||
color: AppColor.textLight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Availability overlay
|
|
||||||
if (!product.isAvailable)
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.black.withOpacity(0.6),
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
top: Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
"HABIS",
|
|
||||||
style: AppStyle.sm.copyWith(
|
|
||||||
color: AppColor.textWhite,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Rating badge
|
|
||||||
Positioned(
|
|
||||||
top: 8,
|
|
||||||
right: 8,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.star, size: 12, color: AppColor.warning),
|
|
||||||
SizedBox(width: 2),
|
|
||||||
Text(
|
|
||||||
"${product.rating}",
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: AppColor.textPrimary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Product info
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
product.name,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColor.textPrimary,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
|
|
||||||
Spacer(),
|
|
||||||
|
|
||||||
// Price and sold count
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Rp ${product.price.currencyFormatRp}",
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColor.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"${product.soldCount} terjual",
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: AppColor.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class ProductEmptyCard extends StatelessWidget {
|
|
||||||
const ProductEmptyCard({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
padding: EdgeInsets.all(24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColor.borderLight),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.inventory_2_outlined,
|
|
||||||
size: 48,
|
|
||||||
color: AppColor.textLight,
|
|
||||||
),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
"Belum ada produk",
|
|
||||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/data/service_data.dart';
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
import '../image/image.dart';
|
|
||||||
import 'gradient_card.dart';
|
|
||||||
|
|
||||||
class ServiceCard extends StatelessWidget {
|
|
||||||
final Service service;
|
|
||||||
const ServiceCard({super.key, required this.service});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GradientCard(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadiusGeometry.circular(8),
|
|
||||||
child: Image.asset(
|
|
||||||
service.imagePath,
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
errorBuilder: (context, error, stackTrace) =>
|
|
||||||
ImagePlaceholder(width: 60, height: 60),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
service.name,
|
|
||||||
style: AppStyle.xl.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColor.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
service.description,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: AppColor.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
child: Text(
|
|
||||||
'Ubah',
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/extension/extension.dart';
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class VariantCard extends StatelessWidget {
|
|
||||||
final String name;
|
|
||||||
final bool isSelected;
|
|
||||||
const VariantCard({super.key, required this.name, this.isSelected = false});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(AppValue.borderRadius),
|
|
||||||
color: isSelected ? AppColor.primary.withOpacity(0.1) : AppColor.white,
|
|
||||||
border: Border.all(
|
|
||||||
width: isSelected ? 2 : 1,
|
|
||||||
color: isSelected ? AppColor.primary : AppColor.border,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
name,
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
"+${"2000".currencyFormatRp}",
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.primary,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
import '../../../sample/product_sample_data.dart';
|
|
||||||
|
|
||||||
class SliverCategoryDelegate extends SliverPersistentHeaderDelegate {
|
|
||||||
final List<ProductCategory> categories;
|
|
||||||
final String selectedCategoryId;
|
|
||||||
final Function(String) onCategoryTap;
|
|
||||||
|
|
||||||
SliverCategoryDelegate({
|
|
||||||
required this.categories,
|
|
||||||
required this.selectedCategoryId,
|
|
||||||
required this.onCategoryTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get minExtent => 60;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get maxExtent => 60;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(
|
|
||||||
BuildContext context,
|
|
||||||
double shrinkOffset,
|
|
||||||
bool overlapsContent,
|
|
||||||
) {
|
|
||||||
return Container(
|
|
||||||
height: 60,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(color: AppColor.borderLight, width: 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
itemCount: categories.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final category = categories[index];
|
|
||||||
final isSelected = category.id == selectedCategoryId;
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onCategoryTap(category.id),
|
|
||||||
child: Container(
|
|
||||||
margin: EdgeInsets.only(right: 12),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? AppColor.primary : AppColor.backgroundLight,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? AppColor.primary : AppColor.border,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(category.icon, style: AppStyle.md),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
category.name,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: isSelected
|
|
||||||
? AppColor.textWhite
|
|
||||||
: AppColor.textPrimary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 4),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected
|
|
||||||
? AppColor.textWhite.withOpacity(0.2)
|
|
||||||
: AppColor.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
"${category.productCount}",
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: isSelected
|
|
||||||
? AppColor.textWhite
|
|
||||||
: AppColor.primary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
|
||||||
return oldDelegate is SliverCategoryDelegate &&
|
|
||||||
(oldDelegate.selectedCategoryId != selectedCategoryId ||
|
|
||||||
oldDelegate.categories != categories);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,225 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/data/service_data.dart';
|
|
||||||
import '../../../common/extension/extension.dart';
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
import '../../../sample/product_sample_data.dart';
|
|
||||||
import '../../components/border/dashed_border.dart';
|
|
||||||
import '../../components/button/button.dart';
|
|
||||||
import '../../components/card/service_card.dart';
|
|
||||||
import 'widgets/checkout_item.dart';
|
|
||||||
import 'widgets/merchant.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class CheckoutPage extends StatelessWidget {
|
|
||||||
const CheckoutPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: Text('Checkout')),
|
|
||||||
bottomNavigationBar: Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding).copyWith(bottom: 24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.white,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.black.withOpacity(0.1),
|
|
||||||
offset: Offset(2, 0),
|
|
||||||
blurRadius: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: AppElevatedButton(onPressed: () {}, title: 'Pesan Sekarang'),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ServiceCard(service: services.first),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
CheckoutMerchant(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(thickness: 4, color: AppColor.borderLight),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_sectionTitle('Pesananmu'),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
CheckoutItem(product: products.first),
|
|
||||||
CheckoutItem(product: products.first),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Ada Tambah lagi?',
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'Kakmu bisa menambah menu lainnya, ya.',
|
|
||||||
style: AppStyle.md,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
AppOutlineButton(
|
|
||||||
width: 100,
|
|
||||||
onPressed: () {},
|
|
||||||
title: '+ Tambah',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(thickness: 4, color: AppColor.borderLight),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_sectionTitle('Voucher'),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
color: AppColor.primary.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.confirmation_number,
|
|
||||||
color: AppColor.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Kamu bisa menggunakan voucher mu disini.',
|
|
||||||
style: AppStyle.md,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(Icons.chevron_right, color: AppColor.textSecondary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(thickness: 4, color: AppColor.borderLight),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_sectionTitle('Metode Pembayaran'),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
color: AppColor.primary.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
child: Icon(Icons.wallet, color: AppColor.primary),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Pilih metode pembayaran disini.',
|
|
||||||
style: AppStyle.md,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(Icons.chevron_right, color: AppColor.textSecondary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(thickness: 4, color: AppColor.borderLight),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_sectionTitle('Rincian Pembayaran'),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Harga',
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"40000".currencyFormatRp,
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: DashedDivider(),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Total Pembayaran',
|
|
||||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"40000".currencyFormatRp,
|
|
||||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(thickness: 4, color: AppColor.borderLight),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Kebijakan Pembatalan",
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'Kamu tidak dapat melakukan pembatalan atau perubahan apapun pada pesanan setelah melakukan pembayaran.',
|
|
||||||
style: AppStyle.sm,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 40),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Text _sectionTitle(String title) {
|
|
||||||
return Text(
|
|
||||||
title,
|
|
||||||
style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../common/extension/extension.dart';
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
import '../../../../sample/product_sample_data.dart';
|
|
||||||
import '../../../components/button/button.dart';
|
|
||||||
import '../../../components/image/image.dart';
|
|
||||||
|
|
||||||
class CheckoutItem extends StatelessWidget {
|
|
||||||
final Product product;
|
|
||||||
const CheckoutItem({super.key, required this.product});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.only(bottom: 16),
|
|
||||||
decoration: BoxDecoration(),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
AppNetworkImage(url: product.imageUrl, width: 60, height: 60),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
product.name,
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"1x ${product.price.currencyFormatRp}",
|
|
||||||
style: AppStyle.md,
|
|
||||||
),
|
|
||||||
Text("27000".currencyFormatRp, style: AppStyle.md),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Catatan',
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.textSecondary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
QtyButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../common/constant/app_constant.dart';
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class CheckoutMerchant extends StatelessWidget {
|
|
||||||
const CheckoutMerchant({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Image.asset(merchant.imageUrl),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
merchant.name,
|
|
||||||
style: AppStyle.h6.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(merchant.category, style: AppStyle.md.copyWith()),
|
|
||||||
Row(children: [SizedBox(width: 12)]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,10 +8,10 @@ import '../../../../../application/customer/customer_point_loader/customer_point
|
|||||||
import '../../../../../common/theme/theme.dart';
|
import '../../../../../common/theme/theme.dart';
|
||||||
import '../../../../components/image/image.dart';
|
import '../../../../components/image/image.dart';
|
||||||
import '../../../../router/app_router.gr.dart';
|
import '../../../../router/app_router.gr.dart';
|
||||||
|
import 'widgets/feature_section.dart';
|
||||||
import 'widgets/lottery_card.dart';
|
import 'widgets/lottery_card.dart';
|
||||||
import 'widgets/point_card.dart';
|
import 'widgets/point_card.dart';
|
||||||
import 'widgets/popular_merchant_section.dart';
|
import 'widgets/popular_merchant_section.dart';
|
||||||
import 'widgets/service_section.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
@ -52,7 +52,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
children: [
|
children: [
|
||||||
_buildHeaderSection(),
|
_buildHeaderSection(),
|
||||||
const SizedBox(height: 70),
|
const SizedBox(height: 70),
|
||||||
HomeServiceSection(),
|
HomeFeatureSection(),
|
||||||
HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())),
|
HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())),
|
||||||
HomePopularMerchantSection(),
|
HomePopularMerchantSection(),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../../../common/data/service_data.dart';
|
|
||||||
import '../../../../../../common/theme/theme.dart';
|
|
||||||
import '../../../../../components/card/gradient_card.dart';
|
|
||||||
import '../../../../../router/app_router.gr.dart';
|
|
||||||
|
|
||||||
class HomeServiceSection extends StatelessWidget {
|
|
||||||
const HomeServiceSection({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: List.generate(services.length, (index) {
|
|
||||||
return Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(right: index == 0 ? 12 : 0),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () =>
|
|
||||||
context.router.push(MenuRoute(service: services[index])),
|
|
||||||
child: GradientCard(
|
|
||||||
child: _content(
|
|
||||||
services[index].name,
|
|
||||||
services[index].description,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Column _content(String title, String subtitle) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: AppStyle.lg.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../common/data/service_data.dart';
|
|
||||||
import '../../../common/theme/theme.dart';
|
|
||||||
import '../../../sample/product_sample_data.dart';
|
|
||||||
import '../../components/delegate/category_delegate.dart';
|
|
||||||
import 'widgets/header.dart';
|
|
||||||
import 'widgets/product_section.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class MenuPage extends StatefulWidget {
|
|
||||||
final Service service;
|
|
||||||
const MenuPage({super.key, required this.service});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MenuPage> createState() => _MenuPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MenuPageState extends State<MenuPage> {
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
final List<GlobalKey> _productSectionKeys = [];
|
|
||||||
|
|
||||||
String _selectedCategoryId = "";
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_selectedCategoryId = categories.isNotEmpty ? categories.first.id : "";
|
|
||||||
|
|
||||||
// Initialize keys for each category
|
|
||||||
for (int i = 0; i < categories.length; i++) {
|
|
||||||
_productSectionKeys.add(GlobalKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _scrollToCategory(String categoryId) {
|
|
||||||
setState(() {
|
|
||||||
_selectedCategoryId = categoryId;
|
|
||||||
});
|
|
||||||
|
|
||||||
final categoryIndex = categories.indexWhere((cat) => cat.id == categoryId);
|
|
||||||
if (categoryIndex >= 0 && categoryIndex < _productSectionKeys.length) {
|
|
||||||
final key = _productSectionKeys[categoryIndex];
|
|
||||||
final context = key.currentContext;
|
|
||||||
|
|
||||||
if (context != null) {
|
|
||||||
Future.delayed(Duration(milliseconds: 100), () {
|
|
||||||
if (mounted) {
|
|
||||||
Scrollable.ensureVisible(
|
|
||||||
context,
|
|
||||||
duration: Duration(milliseconds: 500),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
alignment: 0.1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Product> _getProductsByCategory(String categoryId) {
|
|
||||||
return products
|
|
||||||
.where((product) => product.categoryId == categoryId)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: AppColor.background,
|
|
||||||
body: CustomScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
slivers: [
|
|
||||||
// App Bar with merchant info
|
|
||||||
SliverAppBar(
|
|
||||||
expandedHeight: 200,
|
|
||||||
pinned: true,
|
|
||||||
backgroundColor: AppColor.primary,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: Icon(Icons.arrow_back, color: AppColor.textWhite),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.share, color: AppColor.textWhite),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.favorite_border, color: AppColor.textWhite),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
|
||||||
background: MenuHeader(service: widget.service),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Categories (will be pinned)
|
|
||||||
SliverPersistentHeader(
|
|
||||||
pinned: true,
|
|
||||||
delegate: SliverCategoryDelegate(
|
|
||||||
categories: categories,
|
|
||||||
selectedCategoryId: _selectedCategoryId,
|
|
||||||
onCategoryTap: _scrollToCategory,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Product sections by category
|
|
||||||
...categories.map((category) {
|
|
||||||
final categoryProducts = _getProductsByCategory(category.id);
|
|
||||||
final categoryIndex = categories.indexOf(category);
|
|
||||||
|
|
||||||
return SliverToBoxAdapter(
|
|
||||||
key: _productSectionKeys[categoryIndex],
|
|
||||||
child: MenuProductSection(
|
|
||||||
category: category,
|
|
||||||
categoryProducts: categoryProducts,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../../common/extension/extension.dart';
|
|
||||||
import '../../../../../common/theme/theme.dart';
|
|
||||||
import '../../../../../sample/product_sample_data.dart';
|
|
||||||
import '../../../../components/button/button.dart';
|
|
||||||
import '../../../../components/card/variant_card.dart';
|
|
||||||
import '../../../../components/image/image.dart';
|
|
||||||
import '../../../../router/app_router.gr.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class MenuDetailPage extends StatefulWidget {
|
|
||||||
final Product product;
|
|
||||||
const MenuDetailPage({super.key, required this.product});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MenuDetailPage> createState() => _MenuDetailPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MenuDetailPageState extends State<MenuDetailPage> {
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
double _titleOpacity = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scrollController.addListener(_onScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onScroll() {
|
|
||||||
// Hitung opacity berdasarkan scroll offset
|
|
||||||
// Mulai muncul dari offset 150, full opacity di offset 220
|
|
||||||
double offset = _scrollController.offset;
|
|
||||||
double newOpacity = ((offset - 150) / 70).clamp(0.0, 1.0);
|
|
||||||
|
|
||||||
if (newOpacity != _titleOpacity) {
|
|
||||||
setState(() => _titleOpacity = newOpacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
bottomNavigationBar: Container(
|
|
||||||
padding: EdgeInsets.all(AppValue.padding).copyWith(bottom: 24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.white,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.black.withOpacity(0.1),
|
|
||||||
offset: Offset(2, 0),
|
|
||||||
blurRadius: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Expanded(flex: 1, child: QtyButton()),
|
|
||||||
SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: AppElevatedButton(
|
|
||||||
onPressed: () => context.router.push(CheckoutRoute()),
|
|
||||||
title: '+ Keranjang ${"27000".currencyFormatRp}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: CustomScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
slivers: [
|
|
||||||
SliverAppBar(
|
|
||||||
expandedHeight: 240,
|
|
||||||
pinned: true,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
title: AnimatedOpacity(
|
|
||||||
opacity: _titleOpacity,
|
|
||||||
duration: Duration(milliseconds: 200),
|
|
||||||
child: Text(
|
|
||||||
widget.product.name,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
|
||||||
background: Stack(
|
|
||||||
children: [
|
|
||||||
ImagePlaceholder(width: double.infinity, height: 240),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.product.name,
|
|
||||||
style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
widget.product.description,
|
|
||||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(
|
|
||||||
widget.product.price.currencyFormatRp,
|
|
||||||
style: AppStyle.xxl.copyWith(
|
|
||||||
color: AppColor.primary,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 6,
|
|
||||||
decoration: BoxDecoration(color: AppColor.borderLight),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Pilih Varian',
|
|
||||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
VariantCard(name: 'Small'),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
VariantCard(name: 'Normal', isSelected: true),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
VariantCard(name: 'Large'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../common/constant/app_constant.dart';
|
|
||||||
import '../../../../common/data/service_data.dart';
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class MenuHeader extends StatelessWidget {
|
|
||||||
final Service service;
|
|
||||||
const MenuHeader({super.key, required this.service});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)],
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// Background decoration
|
|
||||||
Positioned(
|
|
||||||
right: 20,
|
|
||||||
top: 60,
|
|
||||||
child: Opacity(
|
|
||||||
opacity: 0.1,
|
|
||||||
child: Icon(Icons.store, size: 100, color: AppColor.textWhite),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Content
|
|
||||||
Positioned(
|
|
||||||
left: 20,
|
|
||||||
right: 20,
|
|
||||||
bottom: 20,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Service
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
service.name,
|
|
||||||
style: AppStyle.lg.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
service.description,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.white,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
SizedBox(height: 16),
|
|
||||||
// Merchant
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.surface,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.black.withOpacity(0.2),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Image.asset(merchant.imageUrl),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
merchant.name,
|
|
||||||
style: AppStyle.h6.copyWith(
|
|
||||||
color: AppColor.textWhite,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
merchant.category,
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.textWhite.withOpacity(0.9),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [SizedBox(width: 12)]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: merchant.isOpen
|
|
||||||
? AppColor.success
|
|
||||||
: AppColor.error,
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
merchant.isOpen ? "BUKA" : "TUTUP",
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: AppColor.textWhite,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
import '../../../../sample/product_sample_data.dart';
|
|
||||||
import '../../../components/card/product_card.dart';
|
|
||||||
import '../../../components/card/product_empty_card.dart';
|
|
||||||
import '../../../router/app_router.gr.dart';
|
|
||||||
|
|
||||||
class MenuProductSection extends StatelessWidget {
|
|
||||||
final ProductCategory category;
|
|
||||||
final List<Product> categoryProducts;
|
|
||||||
const MenuProductSection({
|
|
||||||
super.key,
|
|
||||||
required this.category,
|
|
||||||
required this.categoryProducts,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.only(bottom: 24),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Section header
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(category.icon, style: AppStyle.h5),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
category.name,
|
|
||||||
style: AppStyle.lg.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColor.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
"(${categoryProducts.length})",
|
|
||||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Products grid
|
|
||||||
if (categoryProducts.isEmpty)
|
|
||||||
ProductEmptyCard()
|
|
||||||
else
|
|
||||||
GridView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: NeverScrollableScrollPhysics(),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
crossAxisSpacing: 12,
|
|
||||||
mainAxisSpacing: 12,
|
|
||||||
childAspectRatio: 0.75,
|
|
||||||
),
|
|
||||||
itemCount: categoryProducts.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ProductCard(
|
|
||||||
product: categoryProducts[index],
|
|
||||||
onTap: () => context.router.push(
|
|
||||||
MenuDetailRoute(product: categoryProducts[index]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,47 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../../../../common/theme/theme.dart';
|
import '../../../../../common/theme/theme.dart';
|
||||||
import '../../../../../sample/product_sample_data.dart';
|
|
||||||
import '../../../../../sample/sample_data.dart';
|
import '../../../../../sample/sample_data.dart';
|
||||||
|
|
||||||
|
// Models
|
||||||
|
class ProductCategory {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String icon;
|
||||||
|
final int productCount;
|
||||||
|
|
||||||
|
ProductCategory({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.icon,
|
||||||
|
required this.productCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final int price;
|
||||||
|
final String categoryId;
|
||||||
|
final String imageUrl;
|
||||||
|
final bool isAvailable;
|
||||||
|
final double rating;
|
||||||
|
final int soldCount;
|
||||||
|
|
||||||
|
Product({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.price,
|
||||||
|
required this.categoryId,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.isAvailable,
|
||||||
|
required this.rating,
|
||||||
|
required this.soldCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class MerchantDetailPage extends StatefulWidget {
|
class MerchantDetailPage extends StatefulWidget {
|
||||||
final MerchantModel merchant;
|
final MerchantModel merchant;
|
||||||
@ -20,6 +58,89 @@ class _MerchantDetailPageState extends State<MerchantDetailPage> {
|
|||||||
|
|
||||||
String _selectedCategoryId = "";
|
String _selectedCategoryId = "";
|
||||||
|
|
||||||
|
// Sample data
|
||||||
|
final List<ProductCategory> categories = [
|
||||||
|
ProductCategory(id: "1", name: "Makanan", icon: "🍽️", productCount: 8),
|
||||||
|
ProductCategory(id: "2", name: "Minuman", icon: "🥤", productCount: 6),
|
||||||
|
ProductCategory(id: "3", name: "Snack", icon: "🍿", productCount: 5),
|
||||||
|
ProductCategory(id: "4", name: "Es Krim", icon: "🍦", productCount: 4),
|
||||||
|
ProductCategory(id: "5", name: "Paket", icon: "📦", productCount: 3),
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<Product> products = [
|
||||||
|
// Makanan
|
||||||
|
Product(
|
||||||
|
id: "1",
|
||||||
|
name: "Nasi Gudeg",
|
||||||
|
description: "Gudeg khas Yogyakarta dengan ayam dan telur",
|
||||||
|
price: 25000,
|
||||||
|
categoryId: "1",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: true,
|
||||||
|
rating: 4.5,
|
||||||
|
soldCount: 50,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: "2",
|
||||||
|
name: "Soto Ayam",
|
||||||
|
description: "Soto ayam kuning dengan nasi dan kerupuk",
|
||||||
|
price: 18000,
|
||||||
|
categoryId: "1",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: true,
|
||||||
|
rating: 4.3,
|
||||||
|
soldCount: 75,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: "3",
|
||||||
|
name: "Gado-gado",
|
||||||
|
description: "Gado-gado segar dengan bumbu kacang",
|
||||||
|
price: 15000,
|
||||||
|
categoryId: "1",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: false,
|
||||||
|
rating: 4.2,
|
||||||
|
soldCount: 30,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Minuman
|
||||||
|
Product(
|
||||||
|
id: "4",
|
||||||
|
name: "Es Teh Manis",
|
||||||
|
description: "Es teh manis segar",
|
||||||
|
price: 5000,
|
||||||
|
categoryId: "2",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: true,
|
||||||
|
rating: 4.0,
|
||||||
|
soldCount: 120,
|
||||||
|
),
|
||||||
|
Product(
|
||||||
|
id: "5",
|
||||||
|
name: "Jus Jeruk",
|
||||||
|
description: "Jus jeruk segar tanpa gula tambahan",
|
||||||
|
price: 12000,
|
||||||
|
categoryId: "2",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: true,
|
||||||
|
rating: 4.4,
|
||||||
|
soldCount: 45,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Snack
|
||||||
|
Product(
|
||||||
|
id: "6",
|
||||||
|
name: "Keripik Pisang",
|
||||||
|
description: "Keripik pisang renyah dan manis",
|
||||||
|
price: 8000,
|
||||||
|
categoryId: "3",
|
||||||
|
imageUrl: "https://via.placeholder.com/150",
|
||||||
|
isAvailable: true,
|
||||||
|
rating: 4.1,
|
||||||
|
soldCount: 25,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|||||||
@ -74,12 +74,5 @@ class AppRouter extends RootStackRouter {
|
|||||||
// Mini Games
|
// Mini Games
|
||||||
AutoRoute(page: FerrisWheelRoute.page),
|
AutoRoute(page: FerrisWheelRoute.page),
|
||||||
AutoRoute(page: MisteryBoxRoute.page),
|
AutoRoute(page: MisteryBoxRoute.page),
|
||||||
|
|
||||||
// Menu
|
|
||||||
AutoRoute(page: MenuRoute.page),
|
|
||||||
AutoRoute(page: MenuDetailRoute.page),
|
|
||||||
|
|
||||||
// Checkout
|
|
||||||
AutoRoute(page: CheckoutRoute.page),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,121 +0,0 @@
|
|||||||
// Models
|
|
||||||
class ProductCategory {
|
|
||||||
final String id;
|
|
||||||
final String name;
|
|
||||||
final String icon;
|
|
||||||
final int productCount;
|
|
||||||
|
|
||||||
ProductCategory({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.icon,
|
|
||||||
required this.productCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Product {
|
|
||||||
final String id;
|
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final int price;
|
|
||||||
final String categoryId;
|
|
||||||
final String imageUrl;
|
|
||||||
final bool isAvailable;
|
|
||||||
final double rating;
|
|
||||||
final int soldCount;
|
|
||||||
|
|
||||||
Product({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.description,
|
|
||||||
required this.price,
|
|
||||||
required this.categoryId,
|
|
||||||
required this.imageUrl,
|
|
||||||
required this.isAvailable,
|
|
||||||
required this.rating,
|
|
||||||
required this.soldCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sample data
|
|
||||||
final List<ProductCategory> categories = [
|
|
||||||
ProductCategory(id: "1", name: "Makanan", icon: "🍽️", productCount: 8),
|
|
||||||
ProductCategory(id: "2", name: "Minuman", icon: "🥤", productCount: 6),
|
|
||||||
ProductCategory(id: "3", name: "Snack", icon: "🍿", productCount: 5),
|
|
||||||
ProductCategory(id: "4", name: "Es Krim", icon: "🍦", productCount: 4),
|
|
||||||
ProductCategory(id: "5", name: "Paket", icon: "📦", productCount: 3),
|
|
||||||
];
|
|
||||||
|
|
||||||
final List<Product> products = [
|
|
||||||
// Makanan
|
|
||||||
Product(
|
|
||||||
id: "1",
|
|
||||||
name: "Nasi Gudeg",
|
|
||||||
description: "Gudeg khas Yogyakarta dengan ayam dan telur",
|
|
||||||
price: 25000,
|
|
||||||
categoryId: "1",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: true,
|
|
||||||
rating: 4.5,
|
|
||||||
soldCount: 50,
|
|
||||||
),
|
|
||||||
Product(
|
|
||||||
id: "2",
|
|
||||||
name: "Soto Ayam",
|
|
||||||
description: "Soto ayam kuning dengan nasi dan kerupuk",
|
|
||||||
price: 18000,
|
|
||||||
categoryId: "1",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: true,
|
|
||||||
rating: 4.3,
|
|
||||||
soldCount: 75,
|
|
||||||
),
|
|
||||||
Product(
|
|
||||||
id: "3",
|
|
||||||
name: "Gado-gado",
|
|
||||||
description: "Gado-gado segar dengan bumbu kacang",
|
|
||||||
price: 15000,
|
|
||||||
categoryId: "1",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: false,
|
|
||||||
rating: 4.2,
|
|
||||||
soldCount: 30,
|
|
||||||
),
|
|
||||||
|
|
||||||
// Minuman
|
|
||||||
Product(
|
|
||||||
id: "4",
|
|
||||||
name: "Es Teh Manis",
|
|
||||||
description: "Es teh manis segar",
|
|
||||||
price: 5000,
|
|
||||||
categoryId: "2",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: true,
|
|
||||||
rating: 4.0,
|
|
||||||
soldCount: 120,
|
|
||||||
),
|
|
||||||
Product(
|
|
||||||
id: "5",
|
|
||||||
name: "Jus Jeruk",
|
|
||||||
description: "Jus jeruk segar tanpa gula tambahan",
|
|
||||||
price: 12000,
|
|
||||||
categoryId: "2",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: true,
|
|
||||||
rating: 4.4,
|
|
||||||
soldCount: 45,
|
|
||||||
),
|
|
||||||
|
|
||||||
// Snack
|
|
||||||
Product(
|
|
||||||
id: "6",
|
|
||||||
name: "Keripik Pisang",
|
|
||||||
description: "Keripik pisang renyah dan manis",
|
|
||||||
price: 8000,
|
|
||||||
categoryId: "3",
|
|
||||||
imageUrl: "https://via.placeholder.com/150",
|
|
||||||
isAvailable: true,
|
|
||||||
rating: 4.1,
|
|
||||||
soldCount: 25,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
@ -724,10 +724,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1121,10 +1121,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.6"
|
||||||
time:
|
time:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user