diff --git a/lib/common/theme/theme.dart b/lib/common/theme/theme.dart index 3504646..d1f6f19 100644 --- a/lib/common/theme/theme.dart +++ b/lib/common/theme/theme.dart @@ -26,6 +26,7 @@ class ThemeApp { ), centerTitle: true, iconTheme: IconThemeData(color: AppColor.primary), + scrolledUnderElevation: 0.0, ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( diff --git a/lib/common/ui/clipper/voucher_clipper.dart b/lib/common/ui/clipper/voucher_clipper.dart new file mode 100644 index 0000000..5336e7d --- /dev/null +++ b/lib/common/ui/clipper/voucher_clipper.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class VoucherClipper extends CustomClipper { + @override + Path getClip(Size size) { + final path = Path(); + double notchRadius = 10.0; + double notchPosition = size.height * 0.6; // Position of notch + + // Start from top-left + path.moveTo(0, 12); + + // Top edge with rounded corners + path.quadraticBezierTo(0, 0, 12, 0); + path.lineTo(size.width - 12, 0); + path.quadraticBezierTo(size.width, 0, size.width, 12); + + // Right edge until notch + path.lineTo(size.width, notchPosition - notchRadius); + + // Right notch (semicircle going inward) + path.arcToPoint( + Offset(size.width, notchPosition + notchRadius), + radius: Radius.circular(notchRadius), + clockwise: false, + ); + + // Continue right edge + path.lineTo(size.width, size.height - 12); + + // Bottom edge + path.quadraticBezierTo( + size.width, + size.height, + size.width - 12, + size.height, + ); + path.lineTo(12, size.height); + path.quadraticBezierTo(0, size.height, 0, size.height - 12); + + // Left edge until notch + path.lineTo(0, notchPosition + notchRadius); + + // Left notch (semicircle going inward) + path.arcToPoint( + Offset(0, notchPosition - notchRadius), + radius: Radius.circular(notchRadius), + clockwise: false, + ); + + // Close path + path.close(); + + return path; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} diff --git a/lib/common/ui/painter/dashed_line_painter.dart b/lib/common/ui/painter/dashed_line_painter.dart new file mode 100644 index 0000000..5799a9d --- /dev/null +++ b/lib/common/ui/painter/dashed_line_painter.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class DashedLinePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = Colors.grey[300]! + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + double dashWidth = 5; + double dashSpace = 3; + double startX = 0; + + while (startX < size.width) { + canvas.drawLine( + Offset(startX, size.height / 2), + Offset(startX + dashWidth, size.height / 2), + paint, + ); + startX += dashWidth + dashSpace; + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/pages/main/pages/voucher/voucher_page.dart b/lib/presentation/pages/main/pages/voucher/voucher_page.dart index 2f7f067..0abf080 100644 --- a/lib/presentation/pages/main/pages/voucher/voucher_page.dart +++ b/lib/presentation/pages/main/pages/voucher/voucher_page.dart @@ -1,12 +1,132 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import '../../../../../common/theme/theme.dart'; +import 'widgets/voucher_card.dart'; + @RoutePage() class VoucherPage extends StatelessWidget { const VoucherPage({super.key}); @override Widget build(BuildContext context) { - return Center(child: Text('Voucher Page')); + return Scaffold( + appBar: AppBar( + title: Text('Voucher'), + automaticallyImplyLeading: false, + bottom: PreferredSize( + preferredSize: Size.fromHeight(70), + child: Container( + margin: EdgeInsets.fromLTRB(16, 0, 16, 16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: TextField( + cursorColor: AppColor.primary, + decoration: InputDecoration( + hintText: 'Punya kode promo? Masukkan disini', + hintStyle: TextStyle(color: AppColor.textLight, fontSize: 14), + disabledBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedBorder: InputBorder.none, + prefixIcon: Container( + margin: EdgeInsets.all(12), + width: 24, + height: 24, + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.local_offer, + color: AppColor.white, + size: 14, + ), + ), + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + ), + ), + ), + body: Column( + children: [ + // Voucher Belanja Section + Expanded( + child: Column( + children: [ + // Section Header + Padding( + padding: EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Voucher Belanja', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + '10 voucher', + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), + + // Voucher List + Expanded( + child: ListView( + padding: EdgeInsets.symmetric(horizontal: 16), + children: [ + VoucherCard( + title: 'New User Voucher - Diskon 50% hingga Rp35K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + VoucherCard( + title: 'New User Voucher - Diskon 35% hingga Rp50K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + VoucherCard( + title: 'New User Voucher - Diskon 25% hingga Rp50K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + ], + ), + ), + ], + ), + ), + ], + ), + ); } } diff --git a/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart b/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart new file mode 100644 index 0000000..d6c1cec --- /dev/null +++ b/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../../common/ui/clipper/voucher_clipper.dart'; +import '../../../../../../common/ui/painter/dashed_line_painter.dart'; + +class VoucherCard extends StatelessWidget { + final String title; + final String subtitle; + final String expireDate; + final String minTransaction; + const VoucherCard({ + super.key, + required this.title, + required this.subtitle, + required this.expireDate, + required this.minTransaction, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 12, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 6, + offset: Offset(0, 2), + spreadRadius: 0, + ), + ], + ), + child: ClipPath( + clipper: VoucherClipper(), + child: Container( + decoration: BoxDecoration(color: Colors.white), + child: Column( + children: [ + // Main Content + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 4), + Text( + subtitle, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + SizedBox(width: 12), + // Voucher Icon + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[300]!, width: 1), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.local_offer_outlined, + color: AppColor.primary, + size: 24, + ), + Positioned( + top: 6, + right: 6, + child: Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: AppColor.error, + shape: BoxShape.circle, + ), + child: Icon( + Icons.percent, + color: Colors.white, + size: 10, + ), + ), + ), + ], + ), + ), + ], + ), + ), + + // Dashed line divider + Container( + height: 1, + margin: EdgeInsets.symmetric(horizontal: 20), + child: CustomPaint( + size: Size(double.infinity, 1), + painter: DashedLinePainter(), + ), + ), + + // Bottom Section + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Berlaku Hingga', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontSize: 10, + ), + ), + SizedBox(height: 2), + Text( + expireDate, + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Min Transaksi', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontSize: 10, + ), + ), + SizedBox(height: 2), + Text( + minTransaction, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + SizedBox(width: 16), + // Pakai Button + Container( + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + 'Pakai', + style: TextStyle( + color: AppColor.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart b/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart new file mode 100644 index 0000000..53719d8 --- /dev/null +++ b/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; + +class VoucherEmptyCard extends StatelessWidget { + const VoucherEmptyCard({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Empty State Icon + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.local_offer_outlined, + size: 60, + color: AppColor.primary.withOpacity(0.5), + ), + ), + + SizedBox(height: 24), + + // Empty State Title + Text( + 'Belum Ada Voucher', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + + SizedBox(height: 8), + + // Empty State Description + Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: Text( + 'Kamu belum memiliki voucher saat ini.\nCoba masukkan kode promo di atas atau\ncari promo menarik lainnya.', + textAlign: TextAlign.center, + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + height: 1.5, + ), + ), + ), + + SizedBox(height: 32), + + // Explore Button + ElevatedButton( + onPressed: () { + // Navigate to explore or promo page + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), + ), + child: Text( + 'Jelajahi Promo', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/widgets/bottom_navbar.dart b/lib/presentation/pages/main/widgets/bottom_navbar.dart index db1065c..adb2625 100644 --- a/lib/presentation/pages/main/widgets/bottom_navbar.dart +++ b/lib/presentation/pages/main/widgets/bottom_navbar.dart @@ -19,7 +19,7 @@ class MainBottomNavbar extends StatelessWidget { tooltip: 'Home', ), BottomNavigationBarItem( - icon: Icon(Icons.card_giftcard), + icon: Icon(Icons.discount), label: 'Voucher', tooltip: 'Voucher', ),