diff --git a/assets/icons/dine_in.png b/assets/icons/dine_in.png new file mode 100644 index 0000000..d1d2aa4 Binary files /dev/null and b/assets/icons/dine_in.png differ diff --git a/assets/icons/takeaway.png b/assets/icons/takeaway.png new file mode 100644 index 0000000..3020197 Binary files /dev/null and b/assets/icons/takeaway.png differ diff --git a/lib/common/constant/app_constant.dart b/lib/common/constant/app_constant.dart index b0dbc06..8c0a0f3 100644 --- a/lib/common/constant/app_constant.dart +++ b/lib/common/constant/app_constant.dart @@ -1,3 +1,9 @@ +import '../../sample/sample_data.dart'; + class AppConstant { - static const String appName = ""; + static const String appName = "Enaklo"; + static const String coinName = "EnakCoin"; + static const String poinName = "EnakPoin"; } + +MerchantModel merchant = merchants.first; diff --git a/lib/common/data/service_data.dart b/lib/common/data/service_data.dart new file mode 100644 index 0000000..b5ec3e2 --- /dev/null +++ b/lib/common/data/service_data.dart @@ -0,0 +1,26 @@ +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 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.takeaway.path, + ), +]; diff --git a/lib/common/extension/currency_extension.dart b/lib/common/extension/currency_extension.dart new file mode 100644 index 0000000..e53fa46 --- /dev/null +++ b/lib/common/extension/currency_extension.dart @@ -0,0 +1,28 @@ +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); +} diff --git a/lib/common/extension/extension.dart b/lib/common/extension/extension.dart index a950fef..ece28c3 100644 --- a/lib/common/extension/extension.dart +++ b/lib/common/extension/extension.dart @@ -3,6 +3,7 @@ import 'package:intl/intl.dart'; import '../../domain/auth/auth.dart'; part 'date_extension.dart'; +part 'currency_extension.dart'; extension StringExt on String { CheckPhoneStatus toCheckPhoneStatus() { diff --git a/lib/common/theme/app_value.dart b/lib/common/theme/app_value.dart index 8a1c7a3..873a792 100644 --- a/lib/common/theme/app_value.dart +++ b/lib/common/theme/app_value.dart @@ -1,3 +1,7 @@ part of 'theme.dart'; -class AppValue {} +class AppValue { + static const double padding = 16; + static const double margin = 16; + static const double borderRadius = 12; +} diff --git a/lib/common/theme/theme.dart b/lib/common/theme/theme.dart index 9f00497..2a55eb5 100644 --- a/lib/common/theme/theme.dart +++ b/lib/common/theme/theme.dart @@ -43,7 +43,7 @@ class ThemeApp { foregroundColor: AppColor.textPrimary, elevation: 0, titleTextStyle: AppStyle.xl.copyWith( - color: AppColor.primary, + color: AppColor.textPrimary, fontWeight: FontWeight.w600, ), centerTitle: true, @@ -55,7 +55,18 @@ class ThemeApp { backgroundColor: AppColor.primary, foregroundColor: Colors.white, elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + shape: RoundedRectangleBorder( + 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( diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index cf51d59..73854c9 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -8,6 +8,7 @@ class ApiPath { // Marketing static String ferrisWheel = '/api/v1/customer/ferris-wheel'; + // Customer static String customerPoint = '/api/v1/customer/points'; } diff --git a/lib/env.dart b/lib/env.dart index 031e483..d6c9035 100644 --- a/lib/env.dart +++ b/lib/env.dart @@ -9,12 +9,13 @@ abstract class Env { @dev class DevEnv implements Env { @override - String get baseUrl => 'http://192.168.1.30:4000'; // example value + // String get baseUrl => 'http://192.168.1.30:4000'; // example value + String get baseUrl => 'https://api-pos.apskel.id'; // example value } @Injectable(as: Env) @prod class ProdEnv implements Env { @override - String get baseUrl => 'https://enaklo-pos-be.altru.id'; + String get baseUrl => 'https://api-pos.apskel.id'; } diff --git a/lib/infrastructure/auth/datasources/remote_data_provider.dart b/lib/infrastructure/auth/datasources/remote_data_provider.dart index 579a3ae..f8e7256 100644 --- a/lib/infrastructure/auth/datasources/remote_data_provider.dart +++ b/lib/infrastructure/auth/datasources/remote_data_provider.dart @@ -211,7 +211,9 @@ class AuthRemoteDataProvider { if ((response.data['errors'] as List).isNotEmpty) { if (response.data['errors'][0]['code'] == "900") { return DC.error( - AuthFailure.dynamicErrorMessage('Kamu Belum Terdaftar'), + AuthFailure.dynamicErrorMessage( + response.data['errors'][0]['cause'], + ), ); } else { return DC.error( diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 5bcec36..858c417 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -124,12 +124,12 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i869.CheckPhoneFormBloc>( () => _i869.CheckPhoneFormBloc(gh<_i995.IAuthRepository>()), ); - gh.factory<_i521.VerifyFormBloc>( - () => _i521.VerifyFormBloc(gh<_i995.IAuthRepository>()), - ); gh.factory<_i771.AuthBloc>( () => _i771.AuthBloc(gh<_i995.IAuthRepository>()), ); + gh.factory<_i521.VerifyFormBloc>( + () => _i521.VerifyFormBloc(gh<_i995.IAuthRepository>()), + ); gh.factory<_i216.LogoutFormBloc>( () => _i216.LogoutFormBloc(gh<_i995.IAuthRepository>()), ); diff --git a/lib/presentation/components/assets/assets.gen.dart b/lib/presentation/components/assets/assets.gen.dart index 4ca2ee5..79306ec 100644 --- a/lib/presentation/components/assets/assets.gen.dart +++ b/lib/presentation/components/assets/assets.gen.dart @@ -11,6 +11,52 @@ import 'package:flutter/widgets.dart'; +class $AssetsAudioGen { + const $AssetsAudioGen(); + + /// File path: assets/audio/bell_ding.mp3 + String get bellDing => 'assets/audio/bell_ding.mp3'; + + /// File path: assets/audio/big_win.mp3 + String get bigWin => 'assets/audio/big_win.mp3'; + + /// File path: assets/audio/button_tap.mp3 + String get buttonTap => 'assets/audio/button_tap.mp3'; + + /// File path: assets/audio/carnaval_main_theme.mp3 + String get carnavalMainTheme => 'assets/audio/carnaval_main_theme.mp3'; + + /// File path: assets/audio/token_sound.mp3 + String get tokenSound => 'assets/audio/token_sound.mp3'; + + /// File path: assets/audio/wheel_spin.mp3 + String get wheelSpin => 'assets/audio/wheel_spin.mp3'; + + /// List of all assets + List get values => [ + bellDing, + bigWin, + buttonTap, + carnavalMainTheme, + tokenSound, + wheelSpin, + ]; +} + +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 get values => [dineIn, takeaway]; +} + class $AssetsImagesGen { const $AssetsImagesGen(); @@ -64,6 +110,8 @@ class $AssetsImagesGen { class Assets { const Assets._(); + static const $AssetsAudioGen audio = $AssetsAudioGen(); + static const $AssetsIconsGen icons = $AssetsIconsGen(); static const $AssetsImagesGen images = $AssetsImagesGen(); } diff --git a/lib/presentation/components/border/dashed_border.dart b/lib/presentation/components/border/dashed_border.dart new file mode 100644 index 0000000..5e612f7 --- /dev/null +++ b/lib/presentation/components/border/dashed_border.dart @@ -0,0 +1,39 @@ +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)), + ); + }), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/components/button/button.dart b/lib/presentation/components/button/button.dart index 2cdbda2..aba4042 100644 --- a/lib/presentation/components/button/button.dart +++ b/lib/presentation/components/button/button.dart @@ -4,3 +4,5 @@ import 'package:flutter_spinkit/flutter_spinkit.dart'; import '../../../common/theme/theme.dart'; part 'elevated_button.dart'; +part 'outline_button.dart'; +part 'qty_button.dart'; diff --git a/lib/presentation/components/button/elevated_button.dart b/lib/presentation/components/button/elevated_button.dart index 917fed1..27f99ef 100644 --- a/lib/presentation/components/button/elevated_button.dart +++ b/lib/presentation/components/button/elevated_button.dart @@ -6,7 +6,7 @@ class AppElevatedButton extends StatelessWidget { required this.onPressed, required this.title, this.width = double.infinity, - this.height = 48.0, + this.height = 44.0, this.isLoading = false, }); @@ -41,7 +41,7 @@ class AppElevatedButton extends StatelessWidget { ) : Text( title, - style: AppStyle.lg.copyWith( + style: AppStyle.md.copyWith( color: AppColor.white, fontWeight: FontWeight.w700, ), diff --git a/lib/presentation/components/button/outline_button.dart b/lib/presentation/components/button/outline_button.dart new file mode 100644 index 0000000..921d473 --- /dev/null +++ b/lib/presentation/components/button/outline_button.dart @@ -0,0 +1,58 @@ +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, + ), + ), + ), + ); + } +} diff --git a/lib/presentation/components/button/qty_button.dart b/lib/presentation/components/button/qty_button.dart new file mode 100644 index 0000000..a683ae8 --- /dev/null +++ b/lib/presentation/components/button/qty_button.dart @@ -0,0 +1,38 @@ +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), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/components/card/gradient_card.dart b/lib/presentation/components/card/gradient_card.dart new file mode 100644 index 0000000..31bbb11 --- /dev/null +++ b/lib/presentation/components/card/gradient_card.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +class GradientCard extends StatelessWidget { + final Widget child; + final List? gradientColors; + final double borderRadius; + final EdgeInsetsGeometry? padding; + final bool showDecoration; + + const GradientCard({ + super.key, + required this.child, + this.gradientColors, + this.borderRadius = 16, + this.padding = const EdgeInsets.all(16.0), + this.showDecoration = true, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: gradientColors ?? AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Stack( + children: [ + // Background Pattern (optional) + if (showDecoration) ..._buildDecorations(), + // Main Content + Padding(padding: padding ?? EdgeInsets.zero, child: child), + ], + ), + ); + } + + List _buildDecorations() { + return [ + // Top Right Circle + Positioned( + top: -20, + right: -20, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.1), + ), + ), + ), + // Middle Right Circle + Positioned( + top: 30, + right: 20, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.08), + ), + ), + ), + // Bottom Left Circle + Positioned( + bottom: -10, + left: -10, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.06), + ), + ), + ), + // Top Left Decorative Line + Positioned( + top: 10, + left: -5, + child: Transform.rotate( + angle: 0.5, + child: Container( + width: 30, + height: 2, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ), + // Bottom Right Decorative Line + Positioned( + bottom: 15, + right: 10, + child: Transform.rotate( + angle: -0.5, + child: Container( + width: 25, + height: 2, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ), + ]; + } +} diff --git a/lib/presentation/components/card/product_card.dart b/lib/presentation/components/card/product_card.dart new file mode 100644 index 0000000..df1c45e --- /dev/null +++ b/lib/presentation/components/card/product_card.dart @@ -0,0 +1,153 @@ +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, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/components/card/product_empty_card.dart b/lib/presentation/components/card/product_empty_card.dart new file mode 100644 index 0000000..6304055 --- /dev/null +++ b/lib/presentation/components/card/product_empty_card.dart @@ -0,0 +1,36 @@ +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), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/components/card/service_card.dart b/lib/presentation/components/card/service_card.dart new file mode 100644 index 0000000..e355066 --- /dev/null +++ b/lib/presentation/components/card/service_card.dart @@ -0,0 +1,63 @@ +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, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/components/card/variant_card.dart b/lib/presentation/components/card/variant_card.dart new file mode 100644 index 0000000..9bba116 --- /dev/null +++ b/lib/presentation/components/card/variant_card.dart @@ -0,0 +1,43 @@ +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, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/components/delegate/category_delegate.dart b/lib/presentation/components/delegate/category_delegate.dart new file mode 100644 index 0000000..9a6f050 --- /dev/null +++ b/lib/presentation/components/delegate/category_delegate.dart @@ -0,0 +1,106 @@ +import 'package:flutter/widgets.dart'; + +import '../../../common/theme/theme.dart'; +import '../../../sample/product_sample_data.dart'; + +class SliverCategoryDelegate extends SliverPersistentHeaderDelegate { + final List 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); + } +} diff --git a/lib/presentation/pages/checkout/checkout_page.dart b/lib/presentation/pages/checkout/checkout_page.dart new file mode 100644 index 0000000..0a274fa --- /dev/null +++ b/lib/presentation/pages/checkout/checkout_page.dart @@ -0,0 +1,225 @@ +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), + ); + } +} diff --git a/lib/presentation/pages/checkout/widgets/checkout_item.dart b/lib/presentation/pages/checkout/widgets/checkout_item.dart new file mode 100644 index 0000000..be3dbab --- /dev/null +++ b/lib/presentation/pages/checkout/widgets/checkout_item.dart @@ -0,0 +1,68 @@ +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(), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/checkout/widgets/merchant.dart b/lib/presentation/pages/checkout/widgets/merchant.dart new file mode 100644 index 0000000..0da9195 --- /dev/null +++ b/lib/presentation/pages/checkout/widgets/merchant.dart @@ -0,0 +1,44 @@ +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)]), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/pages/poin/poin_page.dart b/lib/presentation/pages/coin/coin_page.dart similarity index 99% rename from lib/presentation/pages/poin/poin_page.dart rename to lib/presentation/pages/coin/coin_page.dart index b94665a..c725073 100644 --- a/lib/presentation/pages/poin/poin_page.dart +++ b/lib/presentation/pages/coin/coin_page.dart @@ -60,14 +60,14 @@ class Product { } @RoutePage() -class PoinPage extends StatefulWidget { - const PoinPage({super.key}); +class CoinPage extends StatefulWidget { + const CoinPage({super.key}); @override - State createState() => _PoinPageState(); + State createState() => _CoinPageState(); } -class _PoinPageState extends State { +class _CoinPageState extends State { final ScrollController _scrollController = ScrollController(); // Sample data - Indonesian content @@ -276,7 +276,7 @@ class _PoinPageState extends State { actions: [ IconButton( icon: Icon(Icons.history), - onPressed: () => context.router.push(PoinHistoryRoute()), + onPressed: () => context.router.push(CoinHistoryRoute()), ), ], ), diff --git a/lib/presentation/pages/poin/pages/poin_history_page.dart b/lib/presentation/pages/coin/pages/coin_history_page.dart similarity index 98% rename from lib/presentation/pages/poin/pages/poin_history_page.dart rename to lib/presentation/pages/coin/pages/coin_history_page.dart index 391d30c..cb83c98 100644 --- a/lib/presentation/pages/poin/pages/poin_history_page.dart +++ b/lib/presentation/pages/coin/pages/coin_history_page.dart @@ -43,14 +43,14 @@ class PointTransaction { } @RoutePage() -class PoinHistoryPage extends StatefulWidget { - const PoinHistoryPage({super.key}); +class CoinHistoryPage extends StatefulWidget { + const CoinHistoryPage({super.key}); @override - State createState() => _PoinHistoryPageState(); + State createState() => _CoinHistoryPageState(); } -class _PoinHistoryPageState extends State { +class _CoinHistoryPageState extends State { TransactionType selectedFilter = TransactionType.all; // Sample transaction data diff --git a/lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart b/lib/presentation/pages/coin/pages/product_redeem/product_redeem_page.dart similarity index 99% rename from lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart rename to lib/presentation/pages/coin/pages/product_redeem/product_redeem_page.dart index 635c410..a355e22 100644 --- a/lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart +++ b/lib/presentation/pages/coin/pages/product_redeem/product_redeem_page.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../../../common/theme/theme.dart'; -import '../../poin_page.dart'; +import '../../coin_page.dart'; @RoutePage() class ProductRedeemPage extends StatefulWidget { diff --git a/lib/presentation/pages/main/main_page.dart b/lib/presentation/pages/main/main_page.dart index 6e46d27..41ef4e8 100644 --- a/lib/presentation/pages/main/main_page.dart +++ b/lib/presentation/pages/main/main_page.dart @@ -24,7 +24,12 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { return AutoTabsRouter.pageView( - routes: [HomeRoute(), VoucherRoute(), OrderRoute(), ProfileRoute()], + routes: [ + HomeRoute(), + // VoucherRoute(), + OrderRoute(), + ProfileRoute(), + ], physics: const NeverScrollableScrollPhysics(), builder: (context, child, pageController) => Scaffold( body: child, diff --git a/lib/presentation/pages/main/pages/home/home_page.dart b/lib/presentation/pages/main/pages/home/home_page.dart index d92a23c..ded7289 100644 --- a/lib/presentation/pages/main/pages/home/home_page.dart +++ b/lib/presentation/pages/main/pages/home/home_page.dart @@ -5,13 +5,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../../application/auth/auth_bloc.dart'; import '../../../../../application/customer/customer_point_loader/customer_point_loader_bloc.dart'; +import '../../../../../common/constant/app_constant.dart'; import '../../../../../common/theme/theme.dart'; import '../../../../components/image/image.dart'; import '../../../../router/app_router.gr.dart'; -import 'widgets/feature_section.dart'; -import 'widgets/lottery_card.dart'; +import 'widgets/banner_card.dart'; import 'widgets/point_card.dart'; import 'widgets/popular_merchant_section.dart'; +import 'widgets/service_section.dart'; @RoutePage() class HomePage extends StatefulWidget { @@ -52,8 +53,14 @@ class _HomePageState extends State { children: [ _buildHeaderSection(), const SizedBox(height: 70), - HomeFeatureSection(), - HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())), + HomeServiceSection(), + HomeBanner( + title: '🎰 My Rewards', + subtitle: + 'Main dan tepat menangkan produk gratis dari ${AppConstant.appName}.', + actionText: 'Mainkan Sekarang', + onTap: () => context.router.push(DrawRoute()), + ), HomePopularMerchantSection(), ], ), diff --git a/lib/presentation/pages/main/pages/home/widgets/lottery_card.dart b/lib/presentation/pages/main/pages/home/widgets/banner_card.dart similarity index 97% rename from lib/presentation/pages/main/pages/home/widgets/lottery_card.dart rename to lib/presentation/pages/main/pages/home/widgets/banner_card.dart index e516087..f33ae6f 100644 --- a/lib/presentation/pages/main/pages/home/widgets/lottery_card.dart +++ b/lib/presentation/pages/main/pages/home/widgets/banner_card.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import '../../../../../../common/theme/theme.dart'; -class HomeLotteryBanner extends StatefulWidget { - const HomeLotteryBanner({ +class HomeBanner extends StatefulWidget { + const HomeBanner({ super.key, this.onTap, this.title = "🎰 UNDIAN BERHADIAH", @@ -18,11 +18,10 @@ class HomeLotteryBanner extends StatefulWidget { final String actionText; @override - State createState() => _HomeLotteryBannerState(); + State createState() => _HomeBannerState(); } -class _HomeLotteryBannerState extends State - with TickerProviderStateMixin { +class _HomeBannerState extends State with TickerProviderStateMixin { late AnimationController _pulseController; late AnimationController _shimmerController; late AnimationController _floatingController; @@ -290,7 +289,7 @@ class _HomeLotteryBannerState extends State children: [ const Center( child: Icon( - Icons.casino, + Icons.games, color: Colors.white, size: 32, shadows: [ diff --git a/lib/presentation/pages/main/pages/home/widgets/feature_section.dart b/lib/presentation/pages/main/pages/home/widgets/feature_section.dart index bda479a..0c8cb8a 100644 --- a/lib/presentation/pages/main/pages/home/widgets/feature_section.dart +++ b/lib/presentation/pages/main/pages/home/widgets/feature_section.dart @@ -1,52 +1,60 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../../../application/auth/auth_bloc.dart'; -import '../../../../../router/app_router.gr.dart'; -import 'feature_card.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../components/card/gradient_card.dart'; class HomeFeatureSection extends StatelessWidget { const HomeFeatureSection({super.key}); @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - HomeFeatureCard( - icon: Icons.card_giftcard, - title: 'Reward', - iconColor: const Color(0xFF1976D2), - onTap: () => context.router.push(RewardRoute()), + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: GradientCard( + child: _content( + 'Dine In', + 'Rasakan Sensasi Langsung di Meja Kami!', ), - HomeFeatureCard( - icon: Icons.casino, - title: 'Undian', - iconColor: const Color(0xFF7B1FA2), - onTap: () => context.router.push(DrawRoute()), - ), - HomeFeatureCard( - icon: Icons.store, - title: 'Merchant', - iconColor: const Color(0xFF388E3C), - onTap: () => context.router.push(MerchantRoute()), - ), - HomeFeatureCard( - icon: Icons.blur_circular, - title: 'Wheels', - iconColor: const Color(0xFF388E3C), - onTap: () => state.isAuthenticated - ? context.router.push(FerrisWheelRoute()) - : context.router.push(OnboardingRoute()), - ), - ], + ), ), - ); - }, + SizedBox(width: 16), + Expanded( + child: GradientCard( + child: _content( + 'Take Away', + 'Nikmati di Mana Saja, Tetap Mantap!', + ), + ), + ), + ], + ), + ); + } + + 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, + ), + ), + ], ); } } diff --git a/lib/presentation/pages/main/pages/home/widgets/point_card.dart b/lib/presentation/pages/main/pages/home/widgets/point_card.dart index 779276a..bea986b 100644 --- a/lib/presentation/pages/main/pages/home/widgets/point_card.dart +++ b/lib/presentation/pages/main/pages/home/widgets/point_card.dart @@ -16,7 +16,7 @@ class HomePointCard extends StatelessWidget { builder: (context, state) { return GestureDetector( onTap: () => state.isAuthenticated - ? context.router.push(PoinRoute()) + ? context.router.push(CoinRoute()) : context.router.push(OnboardingRoute()), child: Container( decoration: BoxDecoration( diff --git a/lib/presentation/pages/main/pages/home/widgets/service_section.dart b/lib/presentation/pages/main/pages/home/widgets/service_section.dart new file mode 100644 index 0000000..c974de1 --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/service_section.dart @@ -0,0 +1,71 @@ +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( + padding: EdgeInsets.zero, + child: _content(services[index]), + ), + ), + ), + ); + }), + ), + ); + } + + Stack _content(Service service) { + return Stack( + children: [ + Positioned( + right: 4, + top: 4, + child: Image.asset(service.imagePath, width: 60, height: 60), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + service.name, + style: AppStyle.lg.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Text( + service.description, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/pages/main/pages/profile/profile_page.dart b/lib/presentation/pages/main/pages/profile/profile_page.dart index 94cb39b..98b3140 100644 --- a/lib/presentation/pages/main/pages/profile/profile_page.dart +++ b/lib/presentation/pages/main/pages/profile/profile_page.dart @@ -6,6 +6,7 @@ import '../../../../../application/auth/auth_bloc.dart'; import '../../../../../application/auth/logout_form/logout_form_bloc.dart'; import '../../../../../common/theme/theme.dart'; import '../../../../../injection.dart'; +import '../../../../components/card/gradient_card.dart'; import '../../../../components/toast/flushbar.dart'; import '../../../../router/app_router.gr.dart'; @@ -46,165 +47,81 @@ class ProfilePage extends StatelessWidget implements AutoRouteWrapper { onTap: () => state.isAuthenticated ? context.router.push(AccountMyRoute()) : context.router.push(OnboardingRoute()), - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: AppColor.primaryGradient, - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(16), - ), - child: Stack( - children: [ - // Background Pattern - Positioned( - top: -20, - right: -20, - child: Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.1), - ), - ), - ), - Positioned( - top: 30, - right: 20, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.08), - ), - ), - ), - Positioned( - bottom: -10, - left: -10, - child: Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.white.withOpacity(0.06), - ), - ), - ), - // Decorative Lines - Positioned( - top: 10, - left: -5, - child: Transform.rotate( - angle: 0.5, - child: Container( - width: 30, - height: 2, - decoration: BoxDecoration( - color: AppColor.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(1), + child: GradientCard( + child: !state.isAuthenticated + ? Row( + children: [ + Expanded( + child: Text( + 'Silahkan Masuk', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.white, + letterSpacing: 0.5, + ), + ), ), - ), - ), - ), - Positioned( - bottom: 15, - right: 10, - child: Transform.rotate( - angle: -0.5, - child: Container( - width: 25, - height: 2, - decoration: BoxDecoration( - color: AppColor.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(1), - ), - ), - ), - ), - // Main Content - Padding( - padding: const EdgeInsets.all(16.0), - child: !state.isAuthenticated - ? Row( - children: [ - Expanded( - child: Text( - 'Silahkan Masuk', - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.white, - letterSpacing: 0.5, - ), + ], + ) + : Row( + children: [ + // Avatar + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: AppColor.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity( + 0.1, ), - ), - ], - ) - : Row( - children: [ - // Avatar - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: AppColor.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColor.black - .withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Icon( - Icons.person, - size: 30, - color: AppColor.primary, - ), - ), - const SizedBox(width: 16), - // User Info - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - state.user.name, - style: AppStyle.lg.copyWith( - fontWeight: - FontWeight.bold, - color: AppColor.white, - letterSpacing: 0.5, - ), - ), - const SizedBox(height: 4), - Text( - state.user.phoneNumber, - style: AppStyle.sm.copyWith( - color: AppColor.white - .withOpacity(0.9), - ), - ), - ], - ), - ), - // Arrow Icon - Icon( - Icons.arrow_forward_ios, - color: AppColor.white, - size: 14, + blurRadius: 8, + offset: const Offset(0, 2), ), ], ), - ), - ], - ), + child: Icon( + Icons.person, + size: 30, + color: AppColor.primary, + ), + ), + const SizedBox(width: 16), + // User Info + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + state.user.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.white, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + state.user.phoneNumber, + style: AppStyle.sm.copyWith( + color: AppColor.white + .withOpacity(0.9), + ), + ), + ], + ), + ), + // Arrow Icon + Icon( + Icons.arrow_forward_ios, + color: AppColor.white, + size: 14, + ), + ], + ), ), ), ], diff --git a/lib/presentation/pages/main/widgets/bottom_navbar.dart b/lib/presentation/pages/main/widgets/bottom_navbar.dart index adb2625..49dd7c4 100644 --- a/lib/presentation/pages/main/widgets/bottom_navbar.dart +++ b/lib/presentation/pages/main/widgets/bottom_navbar.dart @@ -18,11 +18,11 @@ class MainBottomNavbar extends StatelessWidget { label: 'Home', tooltip: 'Home', ), - BottomNavigationBarItem( - icon: Icon(Icons.discount), - label: 'Voucher', - tooltip: 'Voucher', - ), + // BottomNavigationBarItem( + // icon: Icon(Icons.discount), + // label: 'Voucher', + // tooltip: 'Voucher', + // ), BottomNavigationBarItem( icon: Icon(Icons.list), label: 'Pesanan', diff --git a/lib/presentation/pages/menu/menu_page.dart b/lib/presentation/pages/menu/menu_page.dart new file mode 100644 index 0000000..f906711 --- /dev/null +++ b/lib/presentation/pages/menu/menu_page.dart @@ -0,0 +1,132 @@ +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 createState() => _MenuPageState(); +} + +class _MenuPageState extends State { + final ScrollController _scrollController = ScrollController(); + final List _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 _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, + ), + ); + }), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/menu/pages/menu_detail/menu_detail_page.dart b/lib/presentation/pages/menu/pages/menu_detail/menu_detail_page.dart new file mode 100644 index 0000000..f1cf4e9 --- /dev/null +++ b/lib/presentation/pages/menu/pages/menu_detail/menu_detail_page.dart @@ -0,0 +1,166 @@ +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 createState() => _MenuDetailPageState(); +} + +class _MenuDetailPageState extends State { + 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'), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/menu/widgets/header.dart b/lib/presentation/pages/menu/widgets/header.dart new file mode 100644 index 0000000..3fb076c --- /dev/null +++ b/lib/presentation/pages/menu/widgets/header.dart @@ -0,0 +1,142 @@ +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 + Row( + children: [ + Expanded( + child: 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(width: 12), + Image.asset(service.imagePath, width: 60, height: 60), + ], + ), + + 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, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/menu/widgets/product_section.dart b/lib/presentation/pages/menu/widgets/product_section.dart new file mode 100644 index 0000000..14b687d --- /dev/null +++ b/lib/presentation/pages/menu/widgets/product_section.dart @@ -0,0 +1,77 @@ +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 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]), + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart b/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart index 313a148..37e06d6 100644 --- a/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart +++ b/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart @@ -1,47 +1,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../../../common/theme/theme.dart'; +import '../../../../../sample/product_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() class MerchantDetailPage extends StatefulWidget { final MerchantModel merchant; @@ -58,89 +20,6 @@ class _MerchantDetailPageState extends State { String _selectedCategoryId = ""; - // Sample data - final List 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 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 void initState() { super.initState(); diff --git a/lib/presentation/pages/mini_games/mistery_box/mistery_box_page.dart b/lib/presentation/pages/mini_games/mistery_box/mistery_box_page.dart new file mode 100644 index 0000000..7907af8 --- /dev/null +++ b/lib/presentation/pages/mini_games/mistery_box/mistery_box_page.dart @@ -0,0 +1,880 @@ +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +@RoutePage() +class MisteryBoxPage extends StatefulWidget { + const MisteryBoxPage({super.key}); + + @override + State createState() => _MisteryBoxPageState(); +} + +class _MisteryBoxPageState extends State + with TickerProviderStateMixin { + int coins = 100; + int totalWins = 0; + List prizes = [ + Prize( + name: 'Voucher Rp 100K', + type: 'Voucher', + value: 100000, + rarity: 'Legendary', + chance: 5, + icon: '🎁', + ), + Prize( + name: 'Cashback 50%', + type: 'Cashback', + value: 50, + rarity: 'Epic', + chance: 10, + icon: '💰', + ), + Prize( + name: '500 Poin Reward', + type: 'Poin', + value: 500, + rarity: 'Rare', + chance: 15, + icon: '🏆', + ), + Prize( + name: 'Voucher Rp 25K', + type: 'Voucher', + value: 25000, + rarity: 'Uncommon', + chance: 25, + icon: '🎫', + ), + Prize( + name: '100 Poin Reward', + type: 'Poin', + value: 100, + rarity: 'Common', + chance: 30, + icon: '⭐', + ), + Prize( + name: 'Diskon 10%', + type: 'Diskon', + value: 10, + rarity: 'Common', + chance: 15, + icon: '🍕', + ), + ]; + + bool isOpening = false; + late AnimationController _shakeController; + late AnimationController _rotateController; + late Animation _shakeAnimation; + late Animation _rotateAnimation; + + @override + void initState() { + super.initState(); + _shakeController = AnimationController( + duration: const Duration(milliseconds: 600), + vsync: this, + ); + _rotateController = AnimationController( + duration: const Duration(milliseconds: 600), + vsync: this, + ); + + _shakeAnimation = Tween(begin: 0, end: 15).animate( + CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn), + ); + _rotateAnimation = Tween(begin: 0, end: 2 * pi).animate( + CurvedAnimation(parent: _rotateController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _shakeController.dispose(); + _rotateController.dispose(); + super.dispose(); + } + + void openBox() async { + if (coins < 10 || isOpening) return; + + setState(() { + isOpening = true; + coins -= 10; + }); + + // Animasi shake dan rotate + _shakeController.repeat(reverse: true); + _rotateController.forward(); + await Future.delayed(const Duration(milliseconds: 1200)); + _shakeController.stop(); + _rotateController.reset(); + + // Generate prize + Prize prize = _generatePrize(); + + setState(() { + totalWins++; + isOpening = false; + }); + + // Show dialog + if (mounted) { + _showPrizeDialog(prize); + } + } + + Prize _generatePrize() { + int random = Random().nextInt(100); + int cumulativeChance = 0; + + for (var prize in prizes) { + cumulativeChance += prize.chance; + if (random < cumulativeChance) { + return prize; + } + } + return prizes.last; + } + + void _showPrizeDialog(Prize prize) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => PrizeDialog(prize: prize), + ); + } + + Color _getRarityColor(String rarity) { + switch (rarity) { + case 'Legendary': + return const Color(0xFFFFD700); + case 'Epic': + return const Color(0xFFB429F9); + case 'Rare': + return const Color(0xFF3B82F6); + case 'Uncommon': + return const Color(0xFF10B981); + default: + return const Color(0xFF9CA3AF); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColor.primary, + AppColor.primaryDark, + const Color(0xFF4A0000), + ], + ), + ), + child: SafeArea( + child: Column( + children: [ + _buildHeader(), + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildMysteryBox(), + const SizedBox(height: 40), + _buildOpenButton(), + const SizedBox(height: 24), + _buildPrizeListButton(), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildHeader() { + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '🎁 Mystery Box', + style: AppStyle.h5.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Win Amazing Rewards!', + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.8), + ), + ), + ], + ), + ), + Row( + children: [ + _buildStatCard('🪙', coins.toString()), + const SizedBox(width: 8), + _buildStatCard('🏆', totalWins.toString()), + ], + ), + ], + ), + ); + } + + Widget _buildStatCard(String icon, String value) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.white.withOpacity(0.3), width: 1.5), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(icon, style: const TextStyle(fontSize: 16)), + const SizedBox(width: 6), + Text( + value, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + Widget _buildMysteryBox() { + return AnimatedBuilder( + animation: Listenable.merge([_shakeAnimation, _rotateAnimation]), + builder: (context, child) { + return Transform.translate( + offset: Offset( + sin(_shakeAnimation.value) * 10, + cos(_shakeAnimation.value) * 5, + ), + child: Transform.rotate( + angle: _rotateAnimation.value, + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 30, + offset: const Offset(0, 15), + ), + BoxShadow( + color: AppColor.warning.withOpacity(0.5), + blurRadius: 50, + spreadRadius: isOpening ? 8 : 0, + ), + ], + ), + child: Stack( + children: [ + Positioned( + top: 15, + left: 15, + child: Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: AppColor.warning.withOpacity(0.2), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + bottom: 20, + right: 20, + child: Container( + width: 25, + height: 25, + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.2), + shape: BoxShape.circle, + ), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('🎁', style: TextStyle(fontSize: 70)), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + 'Mystery Box', + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + if (isOpening) ...[ + const Positioned( + top: 25, + right: 25, + child: Text('✨', style: TextStyle(fontSize: 24)), + ), + const Positioned( + bottom: 30, + left: 25, + child: Text('✨', style: TextStyle(fontSize: 24)), + ), + const Positioned( + top: 50, + left: 30, + child: Text('⭐', style: TextStyle(fontSize: 20)), + ), + ], + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildOpenButton() { + bool canOpen = coins >= 10 && !isOpening; + + return GestureDetector( + onTap: canOpen ? openBox : null, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + decoration: BoxDecoration( + color: canOpen ? AppColor.white : AppColor.white.withOpacity(0.3), + borderRadius: BorderRadius.circular(25), + boxShadow: canOpen + ? [ + BoxShadow( + color: AppColor.white.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 6), + ), + ] + : [], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isOpening ? Icons.refresh : Icons.card_giftcard, + color: canOpen ? AppColor.primary : AppColor.textLight, + size: 24, + ), + const SizedBox(width: 10), + Text( + isOpening ? 'Membuka...' : 'Buka Box', + style: AppStyle.lg.copyWith( + color: canOpen ? AppColor.primary : AppColor.textLight, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: canOpen + ? AppColor.warning.withOpacity(0.2) + : AppColor.textLight.withOpacity(0.2), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '10 🪙', + style: AppStyle.xs.copyWith( + color: canOpen ? AppColor.warning : AppColor.textLight, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildPrizeListButton() { + return GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (context) => _buildPrizeListModal(), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.white.withOpacity(0.3), width: 2), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.emoji_events, color: AppColor.white, size: 20), + const SizedBox(width: 8), + Text( + 'Lihat Hadiah Tersedia', + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 4), + const Icon( + Icons.arrow_forward_ios, + color: AppColor.white, + size: 14, + ), + ], + ), + ), + ); + } + + Widget _buildPrizeListModal() { + return Container( + height: MediaQuery.of(context).size.height * 0.7, + decoration: const BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + children: [ + // Handle bar + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.textLight, + borderRadius: BorderRadius.circular(2), + ), + ), + // Header + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Hadiah Tersedia', + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${prizes.length} Items', + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + // Prize Grid + Expanded( + child: GridView.builder( + padding: const EdgeInsets.symmetric(horizontal: 20), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 0.70, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + itemCount: prizes.length, + itemBuilder: (context, index) { + final prize = prizes[index]; + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + _getRarityColor(prize.rarity).withOpacity(0.1), + _getRarityColor(prize.rarity).withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: _getRarityColor(prize.rarity).withOpacity(0.4), + width: 2, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(prize.icon, style: const TextStyle(fontSize: 40)), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: _getRarityColor(prize.rarity), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + prize.rarity, + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + fontSize: 9, + ), + ), + ), + const SizedBox(height: 8), + Text( + prize.name, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + prize.type, + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: _getRarityColor( + prize.rarity, + ).withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.percent, + size: 10, + color: _getRarityColor(prize.rarity), + ), + const SizedBox(width: 2), + Text( + '${prize.chance}%', + style: AppStyle.xs.copyWith( + color: _getRarityColor(prize.rarity), + fontWeight: FontWeight.bold, + fontSize: 10, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ), + ); + } +} + +class PrizeDialog extends StatefulWidget { + final Prize prize; + + const PrizeDialog({Key? key, required this.prize}) : super(key: key); + + @override + State createState() => _PrizeDialogState(); +} + +class _PrizeDialogState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + + _scaleAnimation = Tween( + begin: 0.5, + end: 1.0, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.elasticOut)); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Color _getRarityColor(String rarity) { + switch (rarity) { + case 'Legendary': + return const Color(0xFFFFD700); + case 'Epic': + return const Color(0xFFB429F9); + case 'Rare': + return const Color(0xFF3B82F6); + case 'Uncommon': + return const Color(0xFF10B981); + default: + return const Color(0xFF9CA3AF); + } + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: Dialog( + backgroundColor: Colors.transparent, + child: ScaleTransition( + scale: _scaleAnimation, + child: Container( + constraints: const BoxConstraints(maxWidth: 320), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + border: Border.all( + color: _getRarityColor(widget.prize.rarity), + width: 4, + ), + boxShadow: [ + BoxShadow( + color: _getRarityColor(widget.prize.rarity).withOpacity(0.5), + blurRadius: 30, + offset: const Offset(0, 10), + spreadRadius: 2, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('🎉', style: const TextStyle(fontSize: 50)), + const SizedBox(height: 12), + Text( + 'Selamat!', + style: AppStyle.h4.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + 'Kamu mendapatkan', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 16), + Text(widget.prize.icon, style: const TextStyle(fontSize: 60)), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 6, + ), + decoration: BoxDecoration( + color: _getRarityColor(widget.prize.rarity), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: _getRarityColor( + widget.prize.rarity, + ).withOpacity(0.4), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + widget.prize.rarity.toUpperCase(), + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + ), + const SizedBox(height: 16), + Text( + widget.prize.name, + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + widget.prize.type, + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 24), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.4), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + 'Tutup', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class Prize { + final String name; + final String type; + final int value; + final String rarity; + final int chance; + final String icon; + + Prize({ + required this.name, + required this.type, + required this.value, + required this.rarity, + required this.chance, + required this.icon, + }); +} diff --git a/lib/presentation/pages/point/point_page.dart b/lib/presentation/pages/point/point_page.dart new file mode 100644 index 0000000..3b13606 --- /dev/null +++ b/lib/presentation/pages/point/point_page.dart @@ -0,0 +1,541 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +import '../../../common/constant/app_constant.dart'; +import '../../../common/theme/theme.dart'; + +@RoutePage() +class PointPage extends StatefulWidget { + const PointPage({super.key}); + + @override + State createState() => _PointPageState(); +} + +class _PointPageState extends State { + int _currentPage = 0; + final PageController _pageController = PageController(viewportFraction: 1.0); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + child: Column( + children: [ + // Pink Header with overlapping elements + Stack( + clipBehavior: Clip.none, + children: [ + // Pink Background + Container( + height: 320, + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColor.primary.withOpacity(0.8), + AppColor.primary, + ], + ), + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Back Button + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.3), + shape: BoxShape.circle, + ), + child: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.white, + size: 28, + ), + onPressed: () => Navigator.pop(context), + ), + ), + + const SizedBox(height: 32), + + // Title + Text( + 'Kelola ${AppConstant.poinName} kamu!', + style: AppStyle.h3.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + // Subtitle + Text( + 'Bisa kumpulin dan tukar dari sini.', + style: AppStyle.md.copyWith( + color: AppColor.white.withOpacity(0.9), + ), + ), + ], + ), + ), + ), + ), + + // Point Card - Overlapping + Positioned( + top: 280, + left: 24, + right: 24, + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: AppColor.primary, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.card_giftcard, + color: Colors.white, + size: 32, + ), + ), + const SizedBox(width: 16), + Text( + '50 ${AppConstant.poinName}', + style: AppStyle.h4.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + + // Black Section + const SizedBox(height: 100), + + Container( + color: Colors.white, + width: double.infinity, + child: Column( + children: [ + // Title Section + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + Text( + 'Tukar ${AppConstant.poinName} buat seru-seruan', + style: AppStyle.h4.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + + const SizedBox(height: 40), + + // PageView - Show 2 items per page + SizedBox( + height: 340, + child: PageView( + controller: _pageController, + + onPageChanged: (index) { + setState(() { + _currentPage = index; + }); + }, + children: [ + // Page 1 - 2 cards + Row( + children: [ + Expanded( + child: _buildRewardCard( + title: 'Main Gift Arena\nsekarang!', + subtitle: 'Dapetin s.d. 1jt Coins!', + buttonText: 'Pakai 50 💎', + isWheel: true, + ), + ), + Expanded( + child: _buildRewardCard( + title: 'Putar untuk\nHarapan', + subtitle: 'GoPay Pet', + buttonText: 'Pakai 5 💎', + isWheel: false, + ), + ), + ], + ), + // Page 2 - 2 cards + Row( + children: [ + Expanded( + child: _buildRewardCard( + title: 'Spin & Win\nHadiah!', + subtitle: 'Kesempatan menang besar!', + buttonText: 'Pakai 30 💎', + isWheel: true, + ), + ), + Expanded( + child: _buildRewardCard( + title: 'Lucky Draw\nBerhadiah', + subtitle: 'Coba keberuntungan!', + buttonText: 'Pakai 20 💎', + isWheel: false, + ), + ), + ], + ), + // Page 3 - 1 card + Row( + children: [ + Expanded( + child: _buildRewardCard( + title: 'Mega Prize\nWheel', + subtitle: 'Hadiah hingga 10jt!', + buttonText: 'Pakai 100 💎', + isWheel: true, + ), + ), + const Spacer(), // Empty space for alignment + ], + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Indicators - 3 pages + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(3, (index) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + margin: const EdgeInsets.symmetric(horizontal: 4), + width: _currentPage == index ? 28 : 8, + height: 8, + decoration: BoxDecoration( + color: _currentPage == index + ? AppColor.primary + : Colors.grey.withOpacity(0.3), + borderRadius: BorderRadius.circular(4), + ), + ); + }), + ), + + const SizedBox(height: 40), + + // Bottom Text + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + Text( + 'Ada banyak cara dapet ${AppConstant.poinName}', + style: AppStyle.h5.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Mulai dari main game sampai nonton video.', + style: AppStyle.sm.copyWith( + color: AppColor.textLight, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + const SizedBox(height: 60), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildRewardCard({ + required String title, + required String subtitle, + required String buttonText, + required bool isWheel, + }) { + return Padding( + padding: const EdgeInsets.only(left: 16), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20), + decoration: BoxDecoration( + color: const Color(0xFF2D2D2D), + borderRadius: BorderRadius.circular(24), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Icon + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isWheel + ? const Color(0xFF4FC3F7) + : const Color(0xFFFFC107), + boxShadow: [ + BoxShadow( + color: + (isWheel + ? const Color(0xFF4FC3F7) + : const Color(0xFFFFC107)) + .withOpacity(0.3), + blurRadius: 20, + spreadRadius: 5, + ), + ], + ), + child: Center( + child: Container( + width: 120, + height: 120, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: isWheel + ? CustomPaint(painter: WheelPainter()) + : CustomPaint(painter: CoinPainter()), + ), + ), + ), + + const SizedBox(height: 20), + + Text( + title, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + height: 1.3, + ), + textAlign: TextAlign.center, + ), + + const SizedBox(height: 6), + + Text( + subtitle, + style: AppStyle.sm.copyWith(color: AppColor.textLight), + textAlign: TextAlign.center, + ), + + const SizedBox(height: 18), + + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: Text( + buttonText, + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + } +} + +class WheelPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final radius = size.width / 2; + + // Draw wheel segments + final paint = Paint()..style = PaintingStyle.fill; + + const segments = 8; + final sweepAngle = (2 * math.pi) / segments; + + for (int i = 0; i < segments; i++) { + paint.color = i.isEven + ? const Color(0xFF2196F3) + : const Color(0xFF90CAF9); + + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius * 0.9), + i * sweepAngle, + sweepAngle, + true, + paint, + ); + } + + // Draw center circle + paint.color = const Color(0xFFFFEB3B); + canvas.drawCircle(center, radius * 0.25, paint); + + // Draw pointer at top + final pointerPaint = Paint() + ..color = const Color(0xFFE91E63) + ..style = PaintingStyle.fill; + + final pointerPath = Path(); + pointerPath.moveTo(center.dx, radius * 0.15); + pointerPath.lineTo(center.dx - radius * 0.15, 0); + pointerPath.lineTo(center.dx + radius * 0.15, 0); + pointerPath.close(); + + canvas.drawPath(pointerPath, pointerPaint); + + // Draw white border on pointer + final borderPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 3; + canvas.drawPath(pointerPath, borderPaint); + + // Draw dots around edge + paint.color = const Color(0xFF4FC3F7); + for (int i = 0; i < 12; i++) { + final angle = (i * 2 * math.pi) / 12; + final x = center.dx + radius * 0.95 * math.cos(angle); + final y = center.dy + radius * 0.95 * math.sin(angle); + canvas.drawCircle(Offset(x, y), 4, paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class CoinPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final radius = size.width / 2; + + // Draw outer ring + final ringPaint = Paint() + ..color = const Color(0xFFD4A418) + ..style = PaintingStyle.stroke + ..strokeWidth = 8; + canvas.drawCircle(center, radius * 0.85, ringPaint); + + // Draw inner circle + final innerPaint = Paint() + ..color = const Color(0xFFFFC107) + ..style = PaintingStyle.fill; + canvas.drawCircle(center, radius * 0.75, innerPaint); + + // Draw center ring symbol + final centerRingPaint = Paint() + ..color = const Color(0xFFD4A418) + ..style = PaintingStyle.stroke + ..strokeWidth = 6; + canvas.drawCircle(center, radius * 0.35, centerRingPaint); + + // Draw small colored dots around + final colors = [ + Colors.red, + Colors.blue, + Colors.green, + Colors.purple, + Colors.orange, + Colors.pink, + ]; + + for (int i = 0; i < 6; i++) { + final angle = (i * 2 * math.pi) / 6; + final x = center.dx + radius * 0.6 * math.cos(angle); + final y = center.dy + radius * 0.6 * math.sin(angle); + + final dotPaint = Paint() + ..color = colors[i] + ..style = PaintingStyle.fill; + canvas.drawCircle(Offset(x, y), 6, dotPaint); + } + + // Draw numbers around edge + final textPainter = TextPainter( + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + + for (int i = 0; i < 12; i++) { + final angle = (i * 2 * math.pi) / 12 - math.pi / 2; + final x = center.dx + radius * 0.85 * math.cos(angle); + final y = center.dy + radius * 0.85 * math.sin(angle); + + textPainter.text = TextSpan( + text: '${(i + 1) * 10}', + style: const TextStyle( + color: Color(0xFFD4A418), + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset(x - textPainter.width / 2, y - textPainter.height / 2), + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 32f0bad..001111b 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -35,8 +35,11 @@ class AppRouter extends RootStackRouter { AutoRoute(page: MerchantDetailRoute.page), // Point - AutoRoute(page: PoinRoute.page), - AutoRoute(page: PoinHistoryRoute.page), + AutoRoute(page: PointRoute.page), + + // Coint + AutoRoute(page: CoinRoute.page), + AutoRoute(page: CoinHistoryRoute.page), AutoRoute(page: ProductRedeemRoute.page), // Draw @@ -70,5 +73,13 @@ class AppRouter extends RootStackRouter { // Mini Games AutoRoute(page: FerrisWheelRoute.page), + AutoRoute(page: MisteryBoxRoute.page), + + // Menu + AutoRoute(page: MenuRoute.page), + AutoRoute(page: MenuDetailRoute.page), + + // Checkout + AutoRoute(page: CheckoutRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index af27992..79ddcf8 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -9,74 +9,83 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i33; +import 'package:auto_route/auto_route.dart' as _i38; +import 'package:enaklo/common/data/service_data.dart' as _i41; import 'package:enaklo/presentation/pages/account/account_my/account_my_page.dart' as _i1; import 'package:enaklo/presentation/pages/account/address/address_page.dart' as _i2; import 'package:enaklo/presentation/pages/account/payment/payment_page.dart' - as _i22; -import 'package:enaklo/presentation/pages/auth/create_password/create_password_page.dart' - as _i3; -import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i12; -import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i20; -import 'package:enaklo/presentation/pages/auth/password/password_page.dart' - as _i21; -import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i23; -import 'package:enaklo/presentation/pages/auth/register/register_page.dart' as _i28; -import 'package:enaklo/presentation/pages/draw/draw_page.dart' as _i7; -import 'package:enaklo/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart' - as _i4; -import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart' - as _i5; -import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart' +import 'package:enaklo/presentation/pages/auth/create_password/create_password_page.dart' as _i6; -import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart' - as _i8; -import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart' - as _i9; -import 'package:enaklo/presentation/pages/main/main_page.dart' as _i13; -import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart' - as _i11; -import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' - as _i19; -import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' +import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i15; +import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i26; +import 'package:enaklo/presentation/pages/auth/password/password_page.dart' as _i27; -import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' - as _i32; -import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i15; -import 'package:enaklo/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart' - as _i14; -import 'package:enaklo/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart' - as _i10; -import 'package:enaklo/presentation/pages/notification/notification_page.dart' - as _i16; -import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart' - as _i17; -import 'package:enaklo/presentation/pages/order/order_detail/order_detail_page.dart' - as _i18; -import 'package:enaklo/presentation/pages/poin/pages/poin_history_page.dart' - as _i24; -import 'package:enaklo/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart' - as _i26; -import 'package:enaklo/presentation/pages/poin/poin_page.dart' as _i25; -import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i29; -import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i30; -import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' +import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i29; +import 'package:enaklo/presentation/pages/auth/register/register_page.dart' + as _i33; +import 'package:enaklo/presentation/pages/checkout/checkout_page.dart' as _i3; +import 'package:enaklo/presentation/pages/coin/coin_page.dart' as _i5; +import 'package:enaklo/presentation/pages/coin/pages/coin_history_page.dart' + as _i4; +import 'package:enaklo/presentation/pages/coin/pages/product_redeem/product_redeem_page.dart' as _i31; -import 'package:enaklo/sample/sample_data.dart' as _i35; -import 'package:flutter/material.dart' as _i34; +import 'package:enaklo/presentation/pages/draw/draw_page.dart' as _i10; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart' + as _i7; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart' + as _i8; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart' + as _i9; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart' + as _i11; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart' + as _i12; +import 'package:enaklo/presentation/pages/main/main_page.dart' as _i16; +import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart' + as _i14; +import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' + as _i25; +import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' + as _i32; +import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' + as _i37; +import 'package:enaklo/presentation/pages/menu/menu_page.dart' as _i18; +import 'package:enaklo/presentation/pages/menu/pages/menu_detail/menu_detail_page.dart' + as _i17; +import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i20; +import 'package:enaklo/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart' + as _i19; +import 'package:enaklo/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart' + as _i13; +import 'package:enaklo/presentation/pages/mini_games/mistery_box/mistery_box_page.dart' + as _i21; +import 'package:enaklo/presentation/pages/notification/notification_page.dart' + as _i22; +import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart' + as _i23; +import 'package:enaklo/presentation/pages/order/order_detail/order_detail_page.dart' + as _i24; +import 'package:enaklo/presentation/pages/point/point_page.dart' as _i30; +import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i34; +import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i35; +import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' + as _i36; +import 'package:enaklo/sample/product_sample_data.dart' as _i40; +import 'package:enaklo/sample/sample_data.dart' as _i42; +import 'package:flutter/material.dart' as _i39; /// generated route for /// [_i1.AccountMyPage] -class AccountMyRoute extends _i33.PageRouteInfo { - const AccountMyRoute({List<_i33.PageRouteInfo>? children}) +class AccountMyRoute extends _i38.PageRouteInfo { + const AccountMyRoute({List<_i38.PageRouteInfo>? children}) : super(AccountMyRoute.name, initialChildren: children); static const String name = 'AccountMyRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { return const _i1.AccountMyPage(); @@ -86,13 +95,13 @@ class AccountMyRoute extends _i33.PageRouteInfo { /// generated route for /// [_i2.AddressPage] -class AddressRoute extends _i33.PageRouteInfo { - const AddressRoute({List<_i33.PageRouteInfo>? children}) +class AddressRoute extends _i38.PageRouteInfo { + const AddressRoute({List<_i38.PageRouteInfo>? children}) : super(AddressRoute.name, initialChildren: children); static const String name = 'AddressRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { return const _i2.AddressPage(); @@ -101,12 +110,60 @@ class AddressRoute extends _i33.PageRouteInfo { } /// generated route for -/// [_i3.CreatePasswordPage] -class CreatePasswordRoute extends _i33.PageRouteInfo { +/// [_i3.CheckoutPage] +class CheckoutRoute extends _i38.PageRouteInfo { + const CheckoutRoute({List<_i38.PageRouteInfo>? children}) + : super(CheckoutRoute.name, initialChildren: children); + + static const String name = 'CheckoutRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + return const _i3.CheckoutPage(); + }, + ); +} + +/// generated route for +/// [_i4.CoinHistoryPage] +class CoinHistoryRoute extends _i38.PageRouteInfo { + const CoinHistoryRoute({List<_i38.PageRouteInfo>? children}) + : super(CoinHistoryRoute.name, initialChildren: children); + + static const String name = 'CoinHistoryRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + return const _i4.CoinHistoryPage(); + }, + ); +} + +/// generated route for +/// [_i5.CoinPage] +class CoinRoute extends _i38.PageRouteInfo { + const CoinRoute({List<_i38.PageRouteInfo>? children}) + : super(CoinRoute.name, initialChildren: children); + + static const String name = 'CoinRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + return const _i5.CoinPage(); + }, + ); +} + +/// generated route for +/// [_i6.CreatePasswordPage] +class CreatePasswordRoute extends _i38.PageRouteInfo { CreatePasswordRoute({ - _i34.Key? key, + _i39.Key? key, required String registrationToken, - List<_i33.PageRouteInfo>? children, + List<_i38.PageRouteInfo>? children, }) : super( CreatePasswordRoute.name, args: CreatePasswordRouteArgs( @@ -118,12 +175,12 @@ class CreatePasswordRoute extends _i33.PageRouteInfo { static const String name = 'CreatePasswordRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i33.WrappedRoute( - child: _i3.CreatePasswordPage( + return _i38.WrappedRoute( + child: _i6.CreatePasswordPage( key: args.key, registrationToken: args.registrationToken, ), @@ -135,7 +192,7 @@ class CreatePasswordRoute extends _i33.PageRouteInfo { class CreatePasswordRouteArgs { const CreatePasswordRouteArgs({this.key, required this.registrationToken}); - final _i34.Key? key; + final _i39.Key? key; final String registrationToken; @@ -146,172 +203,246 @@ class CreatePasswordRouteArgs { } /// generated route for -/// [_i4.DrawDetailPage] -class DrawDetailRoute extends _i33.PageRouteInfo { - const DrawDetailRoute({List<_i33.PageRouteInfo>? children}) +/// [_i7.DrawDetailPage] +class DrawDetailRoute extends _i38.PageRouteInfo { + const DrawDetailRoute({List<_i38.PageRouteInfo>? children}) : super(DrawDetailRoute.name, initialChildren: children); static const String name = 'DrawDetailRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i4.DrawDetailPage(); + return const _i7.DrawDetailPage(); }, ); } /// generated route for -/// [_i5.DrawInfoPage] -class DrawInfoRoute extends _i33.PageRouteInfo { - const DrawInfoRoute({List<_i33.PageRouteInfo>? children}) +/// [_i8.DrawInfoPage] +class DrawInfoRoute extends _i38.PageRouteInfo { + const DrawInfoRoute({List<_i38.PageRouteInfo>? children}) : super(DrawInfoRoute.name, initialChildren: children); static const String name = 'DrawInfoRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i5.DrawInfoPage(); + return const _i8.DrawInfoPage(); }, ); } /// generated route for -/// [_i6.DrawMyNumberPage] -class DrawMyNumberRoute extends _i33.PageRouteInfo { - const DrawMyNumberRoute({List<_i33.PageRouteInfo>? children}) +/// [_i9.DrawMyNumberPage] +class DrawMyNumberRoute extends _i38.PageRouteInfo { + const DrawMyNumberRoute({List<_i38.PageRouteInfo>? children}) : super(DrawMyNumberRoute.name, initialChildren: children); static const String name = 'DrawMyNumberRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i6.DrawMyNumberPage(); + return const _i9.DrawMyNumberPage(); }, ); } /// generated route for -/// [_i7.DrawPage] -class DrawRoute extends _i33.PageRouteInfo { - const DrawRoute({List<_i33.PageRouteInfo>? children}) +/// [_i10.DrawPage] +class DrawRoute extends _i38.PageRouteInfo { + const DrawRoute({List<_i38.PageRouteInfo>? children}) : super(DrawRoute.name, initialChildren: children); static const String name = 'DrawRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i7.DrawPage(); + return const _i10.DrawPage(); }, ); } /// generated route for -/// [_i8.DrawTodayPage] -class DrawTodayRoute extends _i33.PageRouteInfo { - const DrawTodayRoute({List<_i33.PageRouteInfo>? children}) +/// [_i11.DrawTodayPage] +class DrawTodayRoute extends _i38.PageRouteInfo { + const DrawTodayRoute({List<_i38.PageRouteInfo>? children}) : super(DrawTodayRoute.name, initialChildren: children); static const String name = 'DrawTodayRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i8.DrawTodayPage(); + return const _i11.DrawTodayPage(); }, ); } /// generated route for -/// [_i9.DrawWinnerPage] -class DrawWinnerRoute extends _i33.PageRouteInfo { - const DrawWinnerRoute({List<_i33.PageRouteInfo>? children}) +/// [_i12.DrawWinnerPage] +class DrawWinnerRoute extends _i38.PageRouteInfo { + const DrawWinnerRoute({List<_i38.PageRouteInfo>? children}) : super(DrawWinnerRoute.name, initialChildren: children); static const String name = 'DrawWinnerRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i9.DrawWinnerPage(); + return const _i12.DrawWinnerPage(); }, ); } /// generated route for -/// [_i10.FerrisWheelPage] -class FerrisWheelRoute extends _i33.PageRouteInfo { - const FerrisWheelRoute({List<_i33.PageRouteInfo>? children}) +/// [_i13.FerrisWheelPage] +class FerrisWheelRoute extends _i38.PageRouteInfo { + const FerrisWheelRoute({List<_i38.PageRouteInfo>? children}) : super(FerrisWheelRoute.name, initialChildren: children); static const String name = 'FerrisWheelRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return _i33.WrappedRoute(child: const _i10.FerrisWheelPage()); + return _i38.WrappedRoute(child: const _i13.FerrisWheelPage()); }, ); } /// generated route for -/// [_i11.HomePage] -class HomeRoute extends _i33.PageRouteInfo { - const HomeRoute({List<_i33.PageRouteInfo>? children}) +/// [_i14.HomePage] +class HomeRoute extends _i38.PageRouteInfo { + const HomeRoute({List<_i38.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i11.HomePage(); + return const _i14.HomePage(); }, ); } /// generated route for -/// [_i12.LoginPage] -class LoginRoute extends _i33.PageRouteInfo { - const LoginRoute({List<_i33.PageRouteInfo>? children}) +/// [_i15.LoginPage] +class LoginRoute extends _i38.PageRouteInfo { + const LoginRoute({List<_i38.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return _i33.WrappedRoute(child: const _i12.LoginPage()); + return _i38.WrappedRoute(child: const _i15.LoginPage()); }, ); } /// generated route for -/// [_i13.MainPage] -class MainRoute extends _i33.PageRouteInfo { - const MainRoute({List<_i33.PageRouteInfo>? children}) +/// [_i16.MainPage] +class MainRoute extends _i38.PageRouteInfo { + const MainRoute({List<_i38.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i13.MainPage(); + return const _i16.MainPage(); }, ); } /// generated route for -/// [_i14.MerchantDetailPage] -class MerchantDetailRoute extends _i33.PageRouteInfo { +/// [_i17.MenuDetailPage] +class MenuDetailRoute extends _i38.PageRouteInfo { + MenuDetailRoute({ + _i39.Key? key, + required _i40.Product product, + List<_i38.PageRouteInfo>? children, + }) : super( + MenuDetailRoute.name, + args: MenuDetailRouteArgs(key: key, product: product), + initialChildren: children, + ); + + static const String name = 'MenuDetailRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i17.MenuDetailPage(key: args.key, product: args.product); + }, + ); +} + +class MenuDetailRouteArgs { + const MenuDetailRouteArgs({this.key, required this.product}); + + final _i39.Key? key; + + final _i40.Product product; + + @override + String toString() { + return 'MenuDetailRouteArgs{key: $key, product: $product}'; + } +} + +/// generated route for +/// [_i18.MenuPage] +class MenuRoute extends _i38.PageRouteInfo { + MenuRoute({ + _i39.Key? key, + required _i41.Service service, + List<_i38.PageRouteInfo>? children, + }) : super( + MenuRoute.name, + args: MenuRouteArgs(key: key, service: service), + initialChildren: children, + ); + + static const String name = 'MenuRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i18.MenuPage(key: args.key, service: args.service); + }, + ); +} + +class MenuRouteArgs { + const MenuRouteArgs({this.key, required this.service}); + + final _i39.Key? key; + + final _i41.Service service; + + @override + String toString() { + return 'MenuRouteArgs{key: $key, service: $service}'; + } +} + +/// generated route for +/// [_i19.MerchantDetailPage] +class MerchantDetailRoute extends _i38.PageRouteInfo { MerchantDetailRoute({ - _i34.Key? key, - required _i35.MerchantModel merchant, - List<_i33.PageRouteInfo>? children, + _i39.Key? key, + required _i42.MerchantModel merchant, + List<_i38.PageRouteInfo>? children, }) : super( MerchantDetailRoute.name, args: MerchantDetailRouteArgs(key: key, merchant: merchant), @@ -320,11 +451,11 @@ class MerchantDetailRoute extends _i33.PageRouteInfo { static const String name = 'MerchantDetailRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i14.MerchantDetailPage(key: args.key, merchant: args.merchant); + return _i19.MerchantDetailPage(key: args.key, merchant: args.merchant); }, ); } @@ -332,9 +463,9 @@ class MerchantDetailRoute extends _i33.PageRouteInfo { class MerchantDetailRouteArgs { const MerchantDetailRouteArgs({this.key, required this.merchant}); - final _i34.Key? key; + final _i39.Key? key; - final _i35.MerchantModel merchant; + final _i42.MerchantModel merchant; @override String toString() { @@ -343,60 +474,76 @@ class MerchantDetailRouteArgs { } /// generated route for -/// [_i15.MerchantPage] -class MerchantRoute extends _i33.PageRouteInfo { - const MerchantRoute({List<_i33.PageRouteInfo>? children}) +/// [_i20.MerchantPage] +class MerchantRoute extends _i38.PageRouteInfo { + const MerchantRoute({List<_i38.PageRouteInfo>? children}) : super(MerchantRoute.name, initialChildren: children); static const String name = 'MerchantRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i15.MerchantPage(); + return const _i20.MerchantPage(); }, ); } /// generated route for -/// [_i16.NotificationPage] -class NotificationRoute extends _i33.PageRouteInfo { - const NotificationRoute({List<_i33.PageRouteInfo>? children}) +/// [_i21.MisteryBoxPage] +class MisteryBoxRoute extends _i38.PageRouteInfo { + const MisteryBoxRoute({List<_i38.PageRouteInfo>? children}) + : super(MisteryBoxRoute.name, initialChildren: children); + + static const String name = 'MisteryBoxRoute'; + + static _i38.PageInfo page = _i38.PageInfo( + name, + builder: (data) { + return const _i21.MisteryBoxPage(); + }, + ); +} + +/// generated route for +/// [_i22.NotificationPage] +class NotificationRoute extends _i38.PageRouteInfo { + const NotificationRoute({List<_i38.PageRouteInfo>? children}) : super(NotificationRoute.name, initialChildren: children); static const String name = 'NotificationRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i16.NotificationPage(); + return const _i22.NotificationPage(); }, ); } /// generated route for -/// [_i17.OnboardingPage] -class OnboardingRoute extends _i33.PageRouteInfo { - const OnboardingRoute({List<_i33.PageRouteInfo>? children}) +/// [_i23.OnboardingPage] +class OnboardingRoute extends _i38.PageRouteInfo { + const OnboardingRoute({List<_i38.PageRouteInfo>? children}) : super(OnboardingRoute.name, initialChildren: children); static const String name = 'OnboardingRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i17.OnboardingPage(); + return const _i23.OnboardingPage(); }, ); } /// generated route for -/// [_i18.OrderDetailPage] -class OrderDetailRoute extends _i33.PageRouteInfo { +/// [_i24.OrderDetailPage] +class OrderDetailRoute extends _i38.PageRouteInfo { OrderDetailRoute({ - _i34.Key? key, - required _i19.Order order, - List<_i33.PageRouteInfo>? children, + _i39.Key? key, + required _i25.Order order, + List<_i38.PageRouteInfo>? children, }) : super( OrderDetailRoute.name, args: OrderDetailRouteArgs(key: key, order: order), @@ -405,11 +552,11 @@ class OrderDetailRoute extends _i33.PageRouteInfo { static const String name = 'OrderDetailRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i18.OrderDetailPage(key: args.key, order: args.order); + return _i24.OrderDetailPage(key: args.key, order: args.order); }, ); } @@ -417,9 +564,9 @@ class OrderDetailRoute extends _i33.PageRouteInfo { class OrderDetailRouteArgs { const OrderDetailRouteArgs({this.key, required this.order}); - final _i34.Key? key; + final _i39.Key? key; - final _i19.Order order; + final _i25.Order order; @override String toString() { @@ -428,29 +575,29 @@ class OrderDetailRouteArgs { } /// generated route for -/// [_i19.OrderPage] -class OrderRoute extends _i33.PageRouteInfo { - const OrderRoute({List<_i33.PageRouteInfo>? children}) +/// [_i25.OrderPage] +class OrderRoute extends _i38.PageRouteInfo { + const OrderRoute({List<_i38.PageRouteInfo>? children}) : super(OrderRoute.name, initialChildren: children); static const String name = 'OrderRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i19.OrderPage(); + return const _i25.OrderPage(); }, ); } /// generated route for -/// [_i20.OtpPage] -class OtpRoute extends _i33.PageRouteInfo { +/// [_i26.OtpPage] +class OtpRoute extends _i38.PageRouteInfo { OtpRoute({ - _i34.Key? key, + _i39.Key? key, required String registrationToken, required String phoneNumber, - List<_i33.PageRouteInfo>? children, + List<_i38.PageRouteInfo>? children, }) : super( OtpRoute.name, args: OtpRouteArgs( @@ -463,12 +610,12 @@ class OtpRoute extends _i33.PageRouteInfo { static const String name = 'OtpRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i33.WrappedRoute( - child: _i20.OtpPage( + return _i38.WrappedRoute( + child: _i26.OtpPage( key: args.key, registrationToken: args.registrationToken, phoneNumber: args.phoneNumber, @@ -485,7 +632,7 @@ class OtpRouteArgs { required this.phoneNumber, }); - final _i34.Key? key; + final _i39.Key? key; final String registrationToken; @@ -498,12 +645,12 @@ class OtpRouteArgs { } /// generated route for -/// [_i21.PasswordPage] -class PasswordRoute extends _i33.PageRouteInfo { +/// [_i27.PasswordPage] +class PasswordRoute extends _i38.PageRouteInfo { PasswordRoute({ - _i34.Key? key, + _i39.Key? key, required String phoneNumber, - List<_i33.PageRouteInfo>? children, + List<_i38.PageRouteInfo>? children, }) : super( PasswordRoute.name, args: PasswordRouteArgs(key: key, phoneNumber: phoneNumber), @@ -512,12 +659,12 @@ class PasswordRoute extends _i33.PageRouteInfo { static const String name = 'PasswordRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i33.WrappedRoute( - child: _i21.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), + return _i38.WrappedRoute( + child: _i27.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), ); }, ); @@ -526,7 +673,7 @@ class PasswordRoute extends _i33.PageRouteInfo { class PasswordRouteArgs { const PasswordRouteArgs({this.key, required this.phoneNumber}); - final _i34.Key? key; + final _i39.Key? key; final String phoneNumber; @@ -537,29 +684,29 @@ class PasswordRouteArgs { } /// generated route for -/// [_i22.PaymentPage] -class PaymentRoute extends _i33.PageRouteInfo { - const PaymentRoute({List<_i33.PageRouteInfo>? children}) +/// [_i28.PaymentPage] +class PaymentRoute extends _i38.PageRouteInfo { + const PaymentRoute({List<_i38.PageRouteInfo>? children}) : super(PaymentRoute.name, initialChildren: children); static const String name = 'PaymentRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i22.PaymentPage(); + return const _i28.PaymentPage(); }, ); } /// generated route for -/// [_i23.PinPage] -class PinRoute extends _i33.PageRouteInfo { +/// [_i29.PinPage] +class PinRoute extends _i38.PageRouteInfo { PinRoute({ - _i34.Key? key, + _i39.Key? key, bool isCreatePin = true, String? title, - List<_i33.PageRouteInfo>? children, + List<_i38.PageRouteInfo>? children, }) : super( PinRoute.name, args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title), @@ -568,13 +715,13 @@ class PinRoute extends _i33.PageRouteInfo { static const String name = 'PinRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const PinRouteArgs(), ); - return _i23.PinPage( + return _i29.PinPage( key: args.key, isCreatePin: args.isCreatePin, title: args.title, @@ -586,7 +733,7 @@ class PinRoute extends _i33.PageRouteInfo { class PinRouteArgs { const PinRouteArgs({this.key, this.isCreatePin = true, this.title}); - final _i34.Key? key; + final _i39.Key? key; final bool isCreatePin; @@ -599,45 +746,29 @@ class PinRouteArgs { } /// generated route for -/// [_i24.PoinHistoryPage] -class PoinHistoryRoute extends _i33.PageRouteInfo { - const PoinHistoryRoute({List<_i33.PageRouteInfo>? children}) - : super(PoinHistoryRoute.name, initialChildren: children); +/// [_i30.PointPage] +class PointRoute extends _i38.PageRouteInfo { + const PointRoute({List<_i38.PageRouteInfo>? children}) + : super(PointRoute.name, initialChildren: children); - static const String name = 'PoinHistoryRoute'; + static const String name = 'PointRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i24.PoinHistoryPage(); + return const _i30.PointPage(); }, ); } /// generated route for -/// [_i25.PoinPage] -class PoinRoute extends _i33.PageRouteInfo { - const PoinRoute({List<_i33.PageRouteInfo>? children}) - : super(PoinRoute.name, initialChildren: children); - - static const String name = 'PoinRoute'; - - static _i33.PageInfo page = _i33.PageInfo( - name, - builder: (data) { - return const _i25.PoinPage(); - }, - ); -} - -/// generated route for -/// [_i26.ProductRedeemPage] -class ProductRedeemRoute extends _i33.PageRouteInfo { +/// [_i31.ProductRedeemPage] +class ProductRedeemRoute extends _i38.PageRouteInfo { ProductRedeemRoute({ - _i34.Key? key, - required _i25.Product product, - required _i25.PointCard pointCard, - List<_i33.PageRouteInfo>? children, + _i39.Key? key, + required _i5.Product product, + required _i5.PointCard pointCard, + List<_i38.PageRouteInfo>? children, }) : super( ProductRedeemRoute.name, args: ProductRedeemRouteArgs( @@ -650,11 +781,11 @@ class ProductRedeemRoute extends _i33.PageRouteInfo { static const String name = 'ProductRedeemRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i26.ProductRedeemPage( + return _i31.ProductRedeemPage( key: args.key, product: args.product, pointCard: args.pointCard, @@ -670,11 +801,11 @@ class ProductRedeemRouteArgs { required this.pointCard, }); - final _i34.Key? key; + final _i39.Key? key; - final _i25.Product product; + final _i5.Product product; - final _i25.PointCard pointCard; + final _i5.PointCard pointCard; @override String toString() { @@ -683,28 +814,28 @@ class ProductRedeemRouteArgs { } /// generated route for -/// [_i27.ProfilePage] -class ProfileRoute extends _i33.PageRouteInfo { - const ProfileRoute({List<_i33.PageRouteInfo>? children}) +/// [_i32.ProfilePage] +class ProfileRoute extends _i38.PageRouteInfo { + const ProfileRoute({List<_i38.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return _i33.WrappedRoute(child: const _i27.ProfilePage()); + return _i38.WrappedRoute(child: const _i32.ProfilePage()); }, ); } /// generated route for -/// [_i28.RegisterPage] -class RegisterRoute extends _i33.PageRouteInfo { +/// [_i33.RegisterPage] +class RegisterRoute extends _i38.PageRouteInfo { RegisterRoute({ - _i34.Key? key, + _i39.Key? key, required String phoneNumber, - List<_i33.PageRouteInfo>? children, + List<_i38.PageRouteInfo>? children, }) : super( RegisterRoute.name, args: RegisterRouteArgs(key: key, phoneNumber: phoneNumber), @@ -713,12 +844,12 @@ class RegisterRoute extends _i33.PageRouteInfo { static const String name = 'RegisterRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i33.WrappedRoute( - child: _i28.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), + return _i38.WrappedRoute( + child: _i33.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), ); }, ); @@ -727,7 +858,7 @@ class RegisterRoute extends _i33.PageRouteInfo { class RegisterRouteArgs { const RegisterRouteArgs({this.key, required this.phoneNumber}); - final _i34.Key? key; + final _i39.Key? key; final String phoneNumber; @@ -738,65 +869,65 @@ class RegisterRouteArgs { } /// generated route for -/// [_i29.RewardPage] -class RewardRoute extends _i33.PageRouteInfo { - const RewardRoute({List<_i33.PageRouteInfo>? children}) +/// [_i34.RewardPage] +class RewardRoute extends _i38.PageRouteInfo { + const RewardRoute({List<_i38.PageRouteInfo>? children}) : super(RewardRoute.name, initialChildren: children); static const String name = 'RewardRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i29.RewardPage(); + return const _i34.RewardPage(); }, ); } /// generated route for -/// [_i30.SplashPage] -class SplashRoute extends _i33.PageRouteInfo { - const SplashRoute({List<_i33.PageRouteInfo>? children}) +/// [_i35.SplashPage] +class SplashRoute extends _i38.PageRouteInfo { + const SplashRoute({List<_i38.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i30.SplashPage(); + return const _i35.SplashPage(); }, ); } /// generated route for -/// [_i31.VoucherDetailPage] -class VoucherDetailRoute extends _i33.PageRouteInfo { - const VoucherDetailRoute({List<_i33.PageRouteInfo>? children}) +/// [_i36.VoucherDetailPage] +class VoucherDetailRoute extends _i38.PageRouteInfo { + const VoucherDetailRoute({List<_i38.PageRouteInfo>? children}) : super(VoucherDetailRoute.name, initialChildren: children); static const String name = 'VoucherDetailRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i31.VoucherDetailPage(); + return const _i36.VoucherDetailPage(); }, ); } /// generated route for -/// [_i32.VoucherPage] -class VoucherRoute extends _i33.PageRouteInfo { - const VoucherRoute({List<_i33.PageRouteInfo>? children}) +/// [_i37.VoucherPage] +class VoucherRoute extends _i38.PageRouteInfo { + const VoucherRoute({List<_i38.PageRouteInfo>? children}) : super(VoucherRoute.name, initialChildren: children); static const String name = 'VoucherRoute'; - static _i33.PageInfo page = _i33.PageInfo( + static _i38.PageInfo page = _i38.PageInfo( name, builder: (data) { - return const _i32.VoucherPage(); + return const _i37.VoucherPage(); }, ); } diff --git a/lib/sample/product_sample_data.dart b/lib/sample/product_sample_data.dart new file mode 100644 index 0000000..d255d99 --- /dev/null +++ b/lib/sample/product_sample_data.dart @@ -0,0 +1,121 @@ +// 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 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 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, + ), +]; diff --git a/macos/Podfile b/macos/Podfile index 29c8eb3..ff5ddb3 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index fc5d148..ee2d4d6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -461,7 +461,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -543,7 +543,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -593,7 +593,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/pubspec.lock b/pubspec.lock index 7ee8410..eeccb95 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -668,26 +668,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -724,10 +724,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1121,10 +1121,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" time: dependency: transitive description: @@ -1249,10 +1249,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: