Compare commits
No commits in common. "627de219cb73e0f004e2a7ecf57b1c3e9b04618b" and "472e9f5f69819b7e2451109301d92c7823be8f6d" have entirely different histories.
627de219cb
...
472e9f5f69
@ -26,7 +26,6 @@ class ThemeApp {
|
||||
),
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: AppColor.primary),
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
@ -54,12 +53,5 @@ class ThemeApp {
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: AppColor.primary,
|
||||
unselectedItemColor: AppColor.textSecondary,
|
||||
backgroundColor: AppColor.white,
|
||||
elevation: 4,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class VoucherClipper extends CustomClipper<Path> {
|
||||
@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<Path> oldClipper) => false;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../assets/assets.gen.dart';
|
||||
|
||||
@ -1,166 +1,79 @@
|
||||
part of 'image.dart';
|
||||
|
||||
class ImagePlaceholder extends StatelessWidget {
|
||||
const ImagePlaceholder({
|
||||
super.key,
|
||||
this.width,
|
||||
this.height,
|
||||
this.showBorderRadius = true,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
final double? width;
|
||||
final double? height;
|
||||
final bool showBorderRadius;
|
||||
final Color? backgroundColor;
|
||||
const ImagePlaceholder({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// Determine the size based on available space or provided dimensions
|
||||
final containerWidth = width ?? constraints.maxWidth;
|
||||
final containerHeight = height ?? constraints.maxHeight;
|
||||
|
||||
// Calculate the minimum dimension to determine if we should show simple or detailed version
|
||||
final minDimension = math.min(
|
||||
containerWidth == double.infinity ? containerHeight : containerWidth,
|
||||
containerHeight == double.infinity ? containerWidth : containerHeight,
|
||||
);
|
||||
|
||||
return Container(
|
||||
width: containerWidth == double.infinity
|
||||
? double.infinity
|
||||
: containerWidth,
|
||||
height: containerHeight == double.infinity ? null : containerHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? const Color(0x4DD9D9D9),
|
||||
borderRadius: showBorderRadius
|
||||
? const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: minDimension < 100
|
||||
? _buildSimpleVersion(minDimension)
|
||||
: _buildDetailedVersion(minDimension),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Simple version for small sizes (< 100px)
|
||||
Widget _buildSimpleVersion(double size) {
|
||||
final iconSize = (size * 0.4).clamp(16.0, 32.0);
|
||||
final fontSize = (size * 0.12).clamp(8.0, 12.0);
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: iconSize * 1.5,
|
||||
height: iconSize * 1.5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(iconSize * 0.75),
|
||||
),
|
||||
child: Center(
|
||||
child: Assets.images.logo.image(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (size > 50) ...[
|
||||
SizedBox(height: size * 0.05),
|
||||
Text(
|
||||
'Enaklo',
|
||||
style: TextStyle(
|
||||
color: AppColor.primary,
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Detailed version for larger sizes (>= 100px)
|
||||
Widget _buildDetailedVersion(double minDimension) {
|
||||
final scaleFactor = minDimension / 200; // Base scale factor
|
||||
|
||||
// Proportional sizes
|
||||
final illustrationSize = (120 * scaleFactor).clamp(80.0, 120.0);
|
||||
final illustrationHeight = (160 * scaleFactor).clamp(100.0, 160.0);
|
||||
final handWidth = (60 * scaleFactor).clamp(30.0, 60.0);
|
||||
final handHeight = (80 * scaleFactor).clamp(40.0, 80.0);
|
||||
final cupWidth = (70 * scaleFactor).clamp(35.0, 70.0);
|
||||
final cupHeight = (90 * scaleFactor).clamp(45.0, 90.0);
|
||||
final logoSize = (40 * scaleFactor).clamp(20.0, 40.0);
|
||||
final fontSize = (12 * scaleFactor).clamp(8.0, 12.0);
|
||||
|
||||
return Container(
|
||||
width: illustrationSize,
|
||||
height: illustrationHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(illustrationSize / 2),
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0x4DD9D9D9), // Light gray with opacity
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Hand
|
||||
Positioned(
|
||||
bottom: illustrationHeight * 0.125, // 20/160 ratio
|
||||
left: illustrationSize * 0.25, // 30/120 ratio
|
||||
child: Container(
|
||||
width: handWidth,
|
||||
height: handHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFDBB3),
|
||||
borderRadius: BorderRadius.circular(handWidth / 2),
|
||||
),
|
||||
// Hand holding coffee illustration
|
||||
Container(
|
||||
width: 120,
|
||||
height: 160,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(60),
|
||||
),
|
||||
),
|
||||
// Coffee cup
|
||||
Positioned(
|
||||
top: illustrationHeight * 0.1875, // 30/160 ratio
|
||||
left: illustrationSize * 0.208, // 25/120 ratio
|
||||
child: Container(
|
||||
width: cupWidth,
|
||||
height: cupHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF4E4BC),
|
||||
borderRadius: BorderRadius.circular(
|
||||
math.max(8.0, 10 * scaleFactor),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo
|
||||
Assets.images.logo.image(
|
||||
width: logoSize,
|
||||
height: logoSize,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: math.max(4.0, 8 * scaleFactor)),
|
||||
if (cupHeight > 50) // Only show text if cup is big enough
|
||||
Text(
|
||||
'Enaklo',
|
||||
style: TextStyle(
|
||||
color: AppColor.primary,
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Hand
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 30,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFDBB3),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Coffee cup
|
||||
Positioned(
|
||||
top: 30,
|
||||
left: 25,
|
||||
child: Container(
|
||||
width: 70,
|
||||
height: 90,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF4E4BC),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Fore logo
|
||||
Assets.images.logo.image(
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Enaklo',
|
||||
style: TextStyle(
|
||||
color: AppColor.primary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -5,7 +5,6 @@ import 'package:flutter/services.dart';
|
||||
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/button/button.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
|
||||
@RoutePage()
|
||||
class OtpPage extends StatefulWidget {
|
||||
@ -83,7 +82,8 @@ class _OtpPageState extends State<OtpPage> {
|
||||
void _verifyCode() {
|
||||
String code = _controllers.map((controller) => controller.text).join();
|
||||
if (code.length == 6) {
|
||||
context.router.push(PinRoute(isCreatePin: true));
|
||||
// Add your verification logic here
|
||||
print('Verifying code: $code');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,331 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/button/button.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
|
||||
@RoutePage()
|
||||
class PinPage extends StatefulWidget {
|
||||
final bool isCreatePin; // true for creating PIN, false for entering PIN
|
||||
final String? title; // Optional custom title
|
||||
|
||||
const PinPage({super.key, this.isCreatePin = true, this.title});
|
||||
|
||||
@override
|
||||
State<PinPage> createState() => _PinPageState();
|
||||
}
|
||||
|
||||
class _PinPageState extends State<PinPage> {
|
||||
final List<TextEditingController> _controllers = List.generate(
|
||||
6,
|
||||
(index) => TextEditingController(),
|
||||
);
|
||||
final List<FocusNode> _focusNodes = List.generate(6, (index) => FocusNode());
|
||||
|
||||
String _firstPin = '';
|
||||
bool _isConfirmingPin = false;
|
||||
bool _isPinMismatch = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _onPinChanged(String value, int index) {
|
||||
if (value.isNotEmpty) {
|
||||
// Move to next field
|
||||
if (index < 5) {
|
||||
_focusNodes[index + 1].requestFocus();
|
||||
} else {
|
||||
// Last field, unfocus and process PIN
|
||||
_focusNodes[index].unfocus();
|
||||
_processPinInput();
|
||||
}
|
||||
} else {
|
||||
// Handle backspace - move to previous field
|
||||
if (index > 0) {
|
||||
_focusNodes[index - 1].requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _processPinInput() {
|
||||
String currentPin = _controllers
|
||||
.map((controller) => controller.text)
|
||||
.join();
|
||||
|
||||
if (currentPin.length == 6) {
|
||||
if (widget.isCreatePin) {
|
||||
if (!_isConfirmingPin) {
|
||||
// First PIN entry - store and ask for confirmation
|
||||
_firstPin = currentPin;
|
||||
setState(() {
|
||||
_isConfirmingPin = true;
|
||||
_isPinMismatch = false;
|
||||
});
|
||||
_clearPinFields();
|
||||
} else {
|
||||
// Confirming PIN
|
||||
if (currentPin == _firstPin) {
|
||||
// PINs match - create PIN
|
||||
_createPin(currentPin);
|
||||
} else {
|
||||
// PINs don't match
|
||||
setState(() {
|
||||
_isPinMismatch = true;
|
||||
});
|
||||
_clearPinFields();
|
||||
// Auto-hide error after 2 seconds
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isPinMismatch = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Entering existing PIN
|
||||
_verifyPin(currentPin);
|
||||
}
|
||||
}
|
||||
|
||||
context.router.push(MainRoute());
|
||||
}
|
||||
|
||||
void _clearPinFields() {
|
||||
for (var controller in _controllers) {
|
||||
controller.clear();
|
||||
}
|
||||
_focusNodes[0].requestFocus();
|
||||
}
|
||||
|
||||
void _createPin(String pin) {
|
||||
// Add your PIN creation logic here
|
||||
print('Creating PIN: $pin');
|
||||
// Navigate to next screen or show success message
|
||||
}
|
||||
|
||||
void _verifyPin(String pin) {
|
||||
// Add your PIN verification logic here
|
||||
print('Verifying PIN: $pin');
|
||||
// Navigate to next screen or show error
|
||||
}
|
||||
|
||||
void _resetPinCreation() {
|
||||
setState(() {
|
||||
_isConfirmingPin = false;
|
||||
_firstPin = '';
|
||||
_isPinMismatch = false;
|
||||
});
|
||||
_clearPinFields();
|
||||
}
|
||||
|
||||
String get _getTitle {
|
||||
if (widget.title != null) return widget.title!;
|
||||
|
||||
if (widget.isCreatePin) {
|
||||
return _isConfirmingPin ? 'Konfirmasi PIN' : 'Buat PIN Baru';
|
||||
} else {
|
||||
return 'Masukan PIN';
|
||||
}
|
||||
}
|
||||
|
||||
String get _getDescription {
|
||||
if (widget.isCreatePin) {
|
||||
if (_isConfirmingPin) {
|
||||
return 'Masukan kembali PIN untuk konfirmasi';
|
||||
} else {
|
||||
return 'Buat PIN 6 digit untuk keamanan akun Anda';
|
||||
}
|
||||
} else {
|
||||
return 'Masukan PIN 6 digit Anda untuk melanjutkan';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (var controller in _controllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
for (var focusNode in _focusNodes) {
|
||||
focusNode.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.isCreatePin ? 'Buat PIN' : 'Masukan PIN'),
|
||||
leading: widget.isCreatePin && _isConfirmingPin
|
||||
? IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: _resetPinCreation,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Title
|
||||
Text(
|
||||
_getTitle,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
_getDescription,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
// Error message for PIN mismatch
|
||||
if (_isPinMismatch) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'PIN tidak sama. Silakan coba lagi.',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.error,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// PIN input fields
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(6, (index) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: index == 5 ? 0 : 8.0),
|
||||
child: TextFormField(
|
||||
controller: _controllers[index],
|
||||
focusNode: _focusNodes[index],
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 1,
|
||||
obscureText: true, // Hide PIN input
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(
|
||||
counterText: '',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(
|
||||
color: _isPinMismatch
|
||||
? AppColor.error
|
||||
: AppColor.border,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(
|
||||
color: _isPinMismatch
|
||||
? AppColor.error
|
||||
: AppColor.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: AppColor.error),
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
cursorColor: AppColor.primary,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isPinMismatch = false;
|
||||
});
|
||||
_onPinChanged(value, index);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Progress indicator for PIN creation
|
||||
if (widget.isCreatePin) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _isConfirmingPin
|
||||
? AppColor.primary
|
||||
: AppColor.border,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Text(
|
||||
_isConfirmingPin ? 'Langkah 2 dari 2' : 'Langkah 1 dari 2',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Continue Button
|
||||
AppElevatedButton(
|
||||
title: widget.isCreatePin
|
||||
? (_isConfirmingPin ? 'Konfirmasi' : 'Lanjutkan')
|
||||
: 'Masuk',
|
||||
onPressed: () {
|
||||
String pin = _controllers
|
||||
.map((controller) => controller.text)
|
||||
.join();
|
||||
if (pin.length == 6) {
|
||||
_processPinInput();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../router/app_router.gr.dart';
|
||||
import 'widgets/bottom_navbar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MainPage extends StatelessWidget {
|
||||
const MainPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AutoTabsRouter.pageView(
|
||||
routes: [HomeRoute(), VoucherRoute(), OrderRoute(), ProfileRoute()],
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
builder: (context, child, pageController) => Scaffold(
|
||||
body: child,
|
||||
bottomNavigationBar: MainBottomNavbar(
|
||||
tabsRouter: AutoTabsRouter.of(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,171 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../components/image/image.dart';
|
||||
import 'widgets/feature_section.dart';
|
||||
import 'widgets/lottery_card.dart';
|
||||
import 'widgets/point_card.dart';
|
||||
import 'widgets/popular_merchant_section.dart';
|
||||
|
||||
@RoutePage()
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
int _currentCarouselIndex = 0;
|
||||
final CarouselSliderController _carouselController =
|
||||
CarouselSliderController();
|
||||
|
||||
final List<String> _carouselImages = [
|
||||
'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=800&h=400&fit=crop',
|
||||
'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=800&h=400&fit=crop',
|
||||
'https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=800&h=400&fit=crop',
|
||||
'https://images.unsplash.com/photo-1574848794584-c740d6a5595f?w=800&h=400&fit=crop',
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeaderSection(),
|
||||
const SizedBox(height: 70),
|
||||
HomeFeatureSection(),
|
||||
HomeLotteryBanner(),
|
||||
HomePopularMerchantSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderSection() {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_buildCarouselBanner(),
|
||||
_buildNotificationButton(),
|
||||
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 225,
|
||||
child: _buildCarouselIndicators(),
|
||||
),
|
||||
Positioned(left: 16, right: 16, top: 240, child: HomePointCard()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Notification Button
|
||||
Widget _buildNotificationButton() {
|
||||
return Positioned(
|
||||
top: MediaQuery.of(context).padding.top + 10,
|
||||
right: 16,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.black.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: AppColor.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 8,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Carousel Banner (Full Width)
|
||||
Widget _buildCarouselBanner() {
|
||||
return CarouselSlider(
|
||||
carouselController: _carouselController,
|
||||
options: CarouselOptions(
|
||||
height: 280,
|
||||
viewportFraction: 1.0, // Full width
|
||||
enlargeCenterPage: false,
|
||||
autoPlay: true,
|
||||
autoPlayInterval: const Duration(seconds: 4),
|
||||
onPageChanged: (index, reason) {
|
||||
setState(() {
|
||||
_currentCarouselIndex = index;
|
||||
});
|
||||
},
|
||||
),
|
||||
items: _carouselImages
|
||||
.skip(1)
|
||||
.map((imageUrl) => _buildImageSlide(imageUrl))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImageSlide(String imageUrl) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Image.network(
|
||||
imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(
|
||||
color: AppColor.textLight,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(color: AppColor.primary),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return ImagePlaceholder();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCarouselIndicators() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(4, (index) {
|
||||
return GestureDetector(
|
||||
onTap: () => _carouselController.animateToPage(index),
|
||||
child: Container(
|
||||
width: _currentCarouselIndex == index ? 24 : 8,
|
||||
height: 8,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: _currentCarouselIndex == index
|
||||
? AppColor.primary
|
||||
: AppColor.textLight,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
|
||||
class HomeFeatureCard extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Color iconColor;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const HomeFeatureCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.iconColor,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomeFeatureCard> createState() => _HomeFeatureCardState();
|
||||
}
|
||||
|
||||
class _HomeFeatureCardState extends State<HomeFeatureCard>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
vsync: this,
|
||||
);
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.95,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTapDown: (_) => _controller.forward(),
|
||||
onTapUp: (_) => _controller.reverse(),
|
||||
onTapCancel: () => _controller.reverse(),
|
||||
child: AnimatedBuilder(
|
||||
animation: _scaleAnimation,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.08),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(widget.icon, color: widget.iconColor, size: 28),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
widget.title,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'feature_card.dart';
|
||||
|
||||
class HomeFeatureSection extends StatelessWidget {
|
||||
const HomeFeatureSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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: () => print('Navigate to Reward'),
|
||||
),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.casino,
|
||||
title: 'Undian',
|
||||
iconColor: const Color(0xFF7B1FA2),
|
||||
onTap: () => print('Navigate to Undian'),
|
||||
),
|
||||
HomeFeatureCard(
|
||||
icon: Icons.store,
|
||||
title: 'Merchant',
|
||||
iconColor: const Color(0xFF388E3C),
|
||||
onTap: () => print('Navigate to Merchant'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,396 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
|
||||
class HomeLotteryBanner extends StatefulWidget {
|
||||
const HomeLotteryBanner({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.title = "🎰 UNDIAN BERHADIAH",
|
||||
this.subtitle = "Kumpulkan voucher untuk menang hadiah menarik!",
|
||||
this.showAnimation = true,
|
||||
this.actionText = "MAIN SEKARANG",
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final bool showAnimation;
|
||||
final String actionText;
|
||||
|
||||
@override
|
||||
State<HomeLotteryBanner> createState() => _HomeLotteryBannerState();
|
||||
}
|
||||
|
||||
class _HomeLotteryBannerState extends State<HomeLotteryBanner>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _pulseController;
|
||||
late AnimationController _shimmerController;
|
||||
late AnimationController _floatingController;
|
||||
late Animation<double> _pulseAnimation;
|
||||
late Animation<double> _shimmerAnimation;
|
||||
late Animation<double> _floatingAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.showAnimation) {
|
||||
// Pulse animation for the whole banner
|
||||
_pulseController = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Shimmer effect for the gradient
|
||||
_shimmerController = AnimationController(
|
||||
duration: const Duration(seconds: 3),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Floating animation for the icon
|
||||
_floatingController = AnimationController(
|
||||
duration: const Duration(seconds: 4),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.02).animate(
|
||||
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_shimmerAnimation = Tween<double>(begin: -2.0, end: 2.0).animate(
|
||||
CurvedAnimation(parent: _shimmerController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_floatingAnimation = Tween<double>(begin: -5.0, end: 5.0).animate(
|
||||
CurvedAnimation(parent: _floatingController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_pulseController.repeat(reverse: true);
|
||||
_shimmerController.repeat(reverse: true);
|
||||
_floatingController.repeat(reverse: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.showAnimation) {
|
||||
_pulseController.dispose();
|
||||
_shimmerController.dispose();
|
||||
_floatingController.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget banner = Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.4),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.orange.withOpacity(0.2),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Main gradient background
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColor.primary,
|
||||
Colors.orange.shade600,
|
||||
Colors.red.shade500,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0.0, 0.6, 1.0],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Top section with icon and text
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Animated floating icon with multiple effects
|
||||
widget.showAnimation
|
||||
? AnimatedBuilder(
|
||||
animation: _floatingAnimation,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, _floatingAnimation.value),
|
||||
child: _buildIcon(),
|
||||
);
|
||||
},
|
||||
)
|
||||
: _buildIcon(),
|
||||
|
||||
const SizedBox(width: 20),
|
||||
|
||||
// Enhanced text section - now expanded fully
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 4,
|
||||
color: Colors.black26,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Bottom action button - full width
|
||||
_buildActionButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Shimmer overlay effect
|
||||
if (widget.showAnimation)
|
||||
AnimatedBuilder(
|
||||
animation: _shimmerAnimation,
|
||||
builder: (context, child) {
|
||||
return Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.white.withOpacity(0.1),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
begin: Alignment(_shimmerAnimation.value, -1),
|
||||
end: Alignment(_shimmerAnimation.value + 0.5, 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Decorative dots pattern
|
||||
Positioned(
|
||||
top: -20,
|
||||
right: -20,
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -10,
|
||||
left: -30,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.orange.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Wrap with gesture detector and animations
|
||||
if (widget.onTap != null) {
|
||||
banner = GestureDetector(onTap: widget.onTap, child: banner);
|
||||
}
|
||||
|
||||
if (widget.showAnimation) {
|
||||
return AnimatedBuilder(
|
||||
animation: _pulseAnimation,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(scale: _pulseAnimation.value, child: banner);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return banner;
|
||||
}
|
||||
|
||||
Widget _buildIcon() {
|
||||
return Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
Colors.yellow.shade300,
|
||||
Colors.orange.shade400,
|
||||
Colors.red.shade500,
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.orange.withOpacity(0.6),
|
||||
blurRadius: 12,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.yellow.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
const Center(
|
||||
child: Icon(
|
||||
Icons.casino,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 4,
|
||||
color: Colors.black26,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Sparkle effects
|
||||
Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.white,
|
||||
blurRadius: 4,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 10,
|
||||
left: 10,
|
||||
child: Container(
|
||||
width: 4,
|
||||
height: 4,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.white, Colors.yellow.shade100],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, -1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.actionText,
|
||||
style: TextStyle(
|
||||
color: AppColor.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
color: AppColor.primary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,227 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
|
||||
class HomePointCard extends StatelessWidget {
|
||||
const HomePointCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.15),
|
||||
spreadRadius: 0,
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildCoinPattern(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.stars,
|
||||
color: AppColor.white,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'148 Poin',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Kamu punya 148 poin',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 40,
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildCoin(
|
||||
right: 0,
|
||||
top: 0,
|
||||
size: 24,
|
||||
color: Colors.amber,
|
||||
),
|
||||
_buildCoin(
|
||||
right: 20,
|
||||
top: 8,
|
||||
size: 20,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildCoin(
|
||||
right: 40,
|
||||
top: 4,
|
||||
size: 18,
|
||||
color: Colors.amber,
|
||||
),
|
||||
_buildCoin(
|
||||
right: 60,
|
||||
top: 12,
|
||||
size: 16,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildCoin(
|
||||
right: 80,
|
||||
top: 8,
|
||||
size: 14,
|
||||
color: Colors.amber,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Tukarkan poinmu dengan hadiah menarik',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.textSecondary,
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoinPattern() {
|
||||
return Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
right: -20,
|
||||
top: -10,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 40,
|
||||
top: 30,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -15,
|
||||
bottom: -20,
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.08),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 60,
|
||||
bottom: 10,
|
||||
child: Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.12),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoin({
|
||||
required double right,
|
||||
required double top,
|
||||
required double size,
|
||||
required Color color,
|
||||
}) {
|
||||
return Positioned(
|
||||
right: right,
|
||||
top: top,
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'\$',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: size * 0.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
import '../../../../../components/image/image.dart';
|
||||
|
||||
class HomePopularMerchantCard extends StatelessWidget {
|
||||
final String merchantName;
|
||||
final String merchantImage;
|
||||
final String category;
|
||||
final double rating;
|
||||
final String distance;
|
||||
final bool isOpen;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const HomePopularMerchantCard({
|
||||
super.key,
|
||||
required this.merchantName,
|
||||
required this.merchantImage,
|
||||
required this.category,
|
||||
required this.rating,
|
||||
required this.distance,
|
||||
this.isOpen = true,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.06),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Image Container
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: AppColor.border,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
merchantImage,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return ImagePlaceholder(width: 60, height: 60);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Title and Category
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
merchantName,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.2,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
Text(
|
||||
category,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// Distance
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 12,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
distance,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// Rating and Status
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
// Status Badge
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isOpen ? AppColor.success : AppColor.error,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
isOpen ? 'OPEN' : 'CLOSED',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Rating
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.star, size: 12, color: AppColor.warning),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
rating.toString(),
|
||||
style: AppStyle.xs.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
import 'popular_merchant_card.dart';
|
||||
|
||||
class HomePopularMerchantSection extends StatelessWidget {
|
||||
const HomePopularMerchantSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Popular Merchants',
|
||||
style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Lihat Semua',
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
HomePopularMerchantCard(
|
||||
merchantName: 'Warung Bu Sari',
|
||||
merchantImage: 'https://via.placeholder.com/280x160',
|
||||
category: 'Indonesian Food',
|
||||
rating: 4.8,
|
||||
distance: '0.5 km',
|
||||
isOpen: true,
|
||||
onTap: () {
|
||||
print('Warung Bu Sari tapped');
|
||||
},
|
||||
),
|
||||
|
||||
HomePopularMerchantCard(
|
||||
merchantName: 'Pizza Corner',
|
||||
merchantImage: 'https://via.placeholder.com/280x160',
|
||||
category: 'Italian Food',
|
||||
rating: 4.6,
|
||||
distance: '1.2 km',
|
||||
isOpen: false,
|
||||
onTap: () {
|
||||
print('Pizza Corner tapped');
|
||||
},
|
||||
),
|
||||
|
||||
HomePopularMerchantCard(
|
||||
merchantName: 'Kopi Kenangan',
|
||||
merchantImage: 'https://via.placeholder.com/280x160',
|
||||
category: 'Coffee & Drinks',
|
||||
rating: 4.9,
|
||||
distance: '0.8 km',
|
||||
isOpen: true,
|
||||
onTap: () {
|
||||
print('Kopi Kenangan tapped');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,353 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import 'widgets/order_card.dart';
|
||||
|
||||
// Model untuk Order
|
||||
class Order {
|
||||
final String id;
|
||||
final String customerName;
|
||||
final DateTime orderDate;
|
||||
final List<OrderItem> items;
|
||||
final double totalAmount;
|
||||
final OrderStatus status;
|
||||
final String? notes;
|
||||
final String? phoneNumber;
|
||||
final String? address;
|
||||
|
||||
Order({
|
||||
required this.id,
|
||||
required this.customerName,
|
||||
required this.orderDate,
|
||||
required this.items,
|
||||
required this.totalAmount,
|
||||
required this.status,
|
||||
this.notes,
|
||||
this.phoneNumber,
|
||||
this.address,
|
||||
});
|
||||
}
|
||||
|
||||
class OrderItem {
|
||||
final String name;
|
||||
final int quantity;
|
||||
final double price;
|
||||
final String? imageUrl;
|
||||
final String? notes;
|
||||
|
||||
OrderItem({
|
||||
required this.name,
|
||||
required this.quantity,
|
||||
required this.price,
|
||||
this.imageUrl,
|
||||
this.notes,
|
||||
});
|
||||
}
|
||||
|
||||
enum OrderStatus { pending, processing, completed, cancelled }
|
||||
|
||||
class OrderPage extends StatefulWidget {
|
||||
const OrderPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OrderPage> createState() => _OrderPageState();
|
||||
}
|
||||
|
||||
class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
bool _isLoading = true;
|
||||
List<Order> _orders = [];
|
||||
|
||||
// Filter states
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 5, vsync: this);
|
||||
_loadOrders();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _loadOrders() {
|
||||
// Simulate loading
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
// Uncomment untuk testing dengan data
|
||||
_orders = _generateSampleOrders();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
List<Order> _generateSampleOrders() {
|
||||
return [
|
||||
Order(
|
||||
id: "ORD-001",
|
||||
customerName: "John Doe",
|
||||
orderDate: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
address: "Jl. Malioboro No. 123, Yogyakarta",
|
||||
items: [
|
||||
OrderItem(
|
||||
name: "Nasi Gudeg",
|
||||
quantity: 2,
|
||||
price: 25000,
|
||||
notes: "Pedas sedang",
|
||||
),
|
||||
OrderItem(name: "Es Teh Manis", quantity: 2, price: 8000),
|
||||
OrderItem(name: "Kerupuk", quantity: 1, price: 5000),
|
||||
],
|
||||
totalAmount: 71000,
|
||||
status: OrderStatus.pending,
|
||||
notes: "Tolong diantar sebelum jam 2 siang",
|
||||
),
|
||||
Order(
|
||||
id: "ORD-002",
|
||||
customerName: "Jane Smith",
|
||||
orderDate: DateTime.now().subtract(const Duration(hours: 1)),
|
||||
address: "Jl. Sultan Agung No. 45, Yogyakarta",
|
||||
items: [
|
||||
OrderItem(
|
||||
name: "Ayam Bakar",
|
||||
quantity: 1,
|
||||
price: 35000,
|
||||
notes: "Tidak pedas",
|
||||
),
|
||||
OrderItem(name: "Nasi Putih", quantity: 1, price: 5000),
|
||||
OrderItem(name: "Lalapan", quantity: 1, price: 8000),
|
||||
],
|
||||
totalAmount: 48000,
|
||||
status: OrderStatus.processing,
|
||||
),
|
||||
Order(
|
||||
id: "ORD-003",
|
||||
customerName: "Bob Wilson",
|
||||
orderDate: DateTime.now().subtract(const Duration(minutes: 30)),
|
||||
phoneNumber: "+62 811-2345-6789",
|
||||
items: [
|
||||
OrderItem(name: "Gado-gado", quantity: 2, price: 20000),
|
||||
OrderItem(name: "Lontong", quantity: 2, price: 3000),
|
||||
OrderItem(name: "Es Jeruk", quantity: 2, price: 10000),
|
||||
],
|
||||
totalAmount: 66000,
|
||||
status: OrderStatus.completed,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: _buildAppBar(),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildTabBar(),
|
||||
Expanded(
|
||||
child: _isLoading ? _buildLoadingState() : _buildOrderContent(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget _buildAppBar() {
|
||||
return AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.white,
|
||||
title: Text('Pesanan'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _showFilterDialog,
|
||||
icon: const Icon(Icons.filter_list, color: AppColor.textSecondary),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _refreshOrders,
|
||||
icon: const Icon(Icons.refresh, color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabBar() {
|
||||
return Container(
|
||||
color: AppColor.white,
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
labelColor: AppColor.primary,
|
||||
unselectedLabelColor: AppColor.textSecondary,
|
||||
indicatorColor: AppColor.primary,
|
||||
indicatorWeight: 3,
|
||||
labelStyle: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
|
||||
tabAlignment: TabAlignment.start,
|
||||
unselectedLabelStyle: AppStyle.md,
|
||||
tabs: const [
|
||||
Tab(text: 'Semua'),
|
||||
Tab(text: 'Menunggu'),
|
||||
Tab(text: 'Diproses'),
|
||||
Tab(text: 'Selesai'),
|
||||
Tab(text: 'Dibatalkan'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderContent() {
|
||||
if (_orders.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildOrderList(_orders),
|
||||
_buildOrderList(
|
||||
_orders.where((o) => o.status == OrderStatus.pending).toList(),
|
||||
),
|
||||
_buildOrderList(
|
||||
_orders.where((o) => o.status == OrderStatus.processing).toList(),
|
||||
),
|
||||
_buildOrderList(
|
||||
_orders.where((o) => o.status == OrderStatus.completed).toList(),
|
||||
),
|
||||
_buildOrderList(
|
||||
_orders.where((o) => o.status == OrderStatus.cancelled).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderList(List<Order> orders) {
|
||||
if (orders.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: _refreshOrders,
|
||||
color: AppColor.primary,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: orders.length,
|
||||
itemBuilder: (context, index) {
|
||||
return OrderCard(order: orders[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(60),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.receipt_long,
|
||||
size: 60,
|
||||
color: AppColor.primary.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Belum Ada Pesanan',
|
||||
style: AppStyle.h6.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Pesanan akan muncul di sini setelah\npelanggan mulai memesan.',
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _refreshOrders,
|
||||
icon: const Icon(Icons.refresh, size: 20),
|
||||
label: const Text('Muat Ulang'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.primary,
|
||||
foregroundColor: AppColor.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(AppColor.primary),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Memuat pesanan...',
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showFilterDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Filter Pesanan',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Add filter options here
|
||||
Text('Opsi filter akan segera hadir...', style: AppStyle.md),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'Tutup',
|
||||
style: AppStyle.md.copyWith(color: AppColor.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _refreshOrders() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
// Uncomment untuk testing dengan data
|
||||
_orders = _generateSampleOrders();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,332 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
import '../order_page.dart';
|
||||
|
||||
class OrderCard extends StatelessWidget {
|
||||
final Order order;
|
||||
const OrderCard({super.key, required this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.06),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _showOrderDetail(order),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
_buildContent(),
|
||||
const SizedBox(height: 16),
|
||||
_buildFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
order.id,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
DateFormat('dd MMM yyyy • HH:mm').format(order.orderDate),
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildStatusChip(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor().withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: _getStatusColor().withOpacity(0.2), width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(_getStatusIcon(), size: 12, color: _getStatusColor()),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
_getStatusText(),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: _getStatusColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.restaurant_menu_outlined,
|
||||
size: 16,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'${order.items.length} item pesanan',
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...order.items
|
||||
.take(3)
|
||||
.map(
|
||||
(item) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${item.quantity}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Rp ${_formatCurrency(item.price * item.quantity)}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (order.items.length > 3) ...[
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'+${order.items.length - 3} item lainnya',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (order.notes != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.warning.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColor.warning.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.sticky_note_2_outlined,
|
||||
size: 14,
|
||||
color: AppColor.warning,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.notes!,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter() {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: AppColor.border.withOpacity(0.3),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
order.address != null
|
||||
? Icons.location_on_outlined
|
||||
: Icons.store_outlined,
|
||||
size: 16,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.address ?? 'Ambil di tempat',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'Total',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
Text(
|
||||
'Rp ${_formatCurrency(order.totalAmount)}',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color _getStatusColor() {
|
||||
switch (order.status) {
|
||||
case OrderStatus.pending:
|
||||
return AppColor.warning;
|
||||
case OrderStatus.processing:
|
||||
return AppColor.info;
|
||||
case OrderStatus.completed:
|
||||
return AppColor.success;
|
||||
case OrderStatus.cancelled:
|
||||
return AppColor.error;
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatusText() {
|
||||
switch (order.status) {
|
||||
case OrderStatus.pending:
|
||||
return 'Menunggu';
|
||||
case OrderStatus.processing:
|
||||
return 'Diproses';
|
||||
case OrderStatus.completed:
|
||||
return 'Selesai';
|
||||
case OrderStatus.cancelled:
|
||||
return 'Dibatalkan';
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getStatusIcon() {
|
||||
switch (order.status) {
|
||||
case OrderStatus.pending:
|
||||
return Icons.schedule;
|
||||
case OrderStatus.processing:
|
||||
return Icons.hourglass_empty;
|
||||
case OrderStatus.completed:
|
||||
return Icons.check_circle;
|
||||
case OrderStatus.cancelled:
|
||||
return Icons.cancel;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatCurrency(double amount) {
|
||||
final formatter = NumberFormat('#,###');
|
||||
return formatter.format(amount);
|
||||
}
|
||||
|
||||
void _showOrderDetail(Order order) {
|
||||
// Implementation for showing order details
|
||||
}
|
||||
}
|
||||
@ -1,548 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfilePage extends StatelessWidget {
|
||||
const ProfilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
appBar: AppBar(title: Text('Akun')),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Profile Header
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: AppColor.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
// Profile Avatar & Info
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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: 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(
|
||||
'EFRIL',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'+6283873987851',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Arrow Icon
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.white,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Share the Sip Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.backgroundLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Share Icon
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.success.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.share,
|
||||
color: AppColor.success,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Share Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Share the Sip',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Bagikan kode referral, dapatkan hadiah',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.textSecondary,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Menu Items
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildMenuItem(
|
||||
icon: Icons.location_on_outlined,
|
||||
title: 'Alamat Tersimpan',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.payment_outlined,
|
||||
title: 'Pembayaran',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.help_outline,
|
||||
title: 'Pusat Bantuan',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.settings_outlined,
|
||||
title: 'Pengaturan',
|
||||
onTap: () {},
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Legal & Privacy Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildMenuItem(
|
||||
icon: Icons.description_outlined,
|
||||
title: 'Syarat dan Ketentuan',
|
||||
onTap: () {},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.privacy_tip_outlined,
|
||||
title: 'Kebijakan Privasi',
|
||||
onTap: () {},
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Social Media Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Media Sosial',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildSocialButton(
|
||||
icon: Icons.camera_alt,
|
||||
color: Colors.purple,
|
||||
onTap: () => _launchSocialMedia('instagram'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.facebook,
|
||||
color: Colors.blue,
|
||||
onTap: () => _launchSocialMedia('facebook'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.play_arrow,
|
||||
color: Colors.red,
|
||||
onTap: () => _launchSocialMedia('youtube'),
|
||||
),
|
||||
_buildSocialButton(
|
||||
icon: Icons.close,
|
||||
color: Colors.black,
|
||||
onTap: () => _launchSocialMedia('twitter'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Customer Service Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Butuh Bantuan?',
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Customer Service kami siap untuk membantu',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// WhatsApp Customer Service
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.backgroundLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.chat,
|
||||
color: AppColor.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Enaklo Customer Service (chat only)',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'0812-1111-8456',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.success,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.textSecondary,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Footer Section
|
||||
Container(
|
||||
color: AppColor.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Version 4.6.1',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => _showLogoutDialog(context),
|
||||
child: Text(
|
||||
'Logout',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 100), // Bottom spacing
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMenuItem({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required VoidCallback onTap,
|
||||
bool showDivider = true,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(icon, color: AppColor.textSecondary, size: 24),
|
||||
title: Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(color: AppColor.textPrimary),
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.textSecondary,
|
||||
size: 16,
|
||||
),
|
||||
onTap: onTap,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
if (showDivider)
|
||||
Divider(height: 1, color: AppColor.borderLight, indent: 60),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSocialButton({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
child: Icon(icon, color: AppColor.white, size: 24),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _launchSocialMedia(String platform) async {
|
||||
String url = '';
|
||||
switch (platform) {
|
||||
case 'instagram':
|
||||
url = 'https://instagram.com/';
|
||||
break;
|
||||
case 'facebook':
|
||||
url = 'https://facebook.com/';
|
||||
break;
|
||||
case 'youtube':
|
||||
url = 'https://youtube.com/';
|
||||
break;
|
||||
case 'twitter':
|
||||
url = 'https://twitter.com/';
|
||||
break;
|
||||
}
|
||||
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
}
|
||||
|
||||
void _showLogoutDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Logout',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
content: Text(
|
||||
'Apakah Anda yakin ingin keluar dari aplikasi?',
|
||||
style: AppStyle.md,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
'Batal',
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
'Logout',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
// Add logout logic here
|
||||
// Example: context.router.pushAndClearStack('/login');
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
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 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,209 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MainBottomNavbar extends StatelessWidget {
|
||||
final TabsRouter tabsRouter;
|
||||
const MainBottomNavbar({super.key, required this.tabsRouter});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
currentIndex: tabsRouter.activeIndex,
|
||||
onTap: (index) {
|
||||
tabsRouter.setActiveIndex(index);
|
||||
},
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
label: 'Home',
|
||||
tooltip: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.discount),
|
||||
label: 'Voucher',
|
||||
tooltip: 'Voucher',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.list),
|
||||
label: 'Pesanan',
|
||||
tooltip: 'Pesanan',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: 'Profil',
|
||||
tooltip: 'Profil',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -15,17 +15,5 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: LoginRoute.page),
|
||||
AutoRoute(page: RegisterRoute.page),
|
||||
AutoRoute(page: OtpRoute.page),
|
||||
AutoRoute(page: PinRoute.page),
|
||||
|
||||
// Main
|
||||
AutoRoute(
|
||||
page: MainRoute.page,
|
||||
children: [
|
||||
AutoRoute(page: HomeRoute.page),
|
||||
AutoRoute(page: VoucherRoute.page),
|
||||
AutoRoute(page: OrderRoute.page),
|
||||
AutoRoute(page: ProfileRoute.page),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -9,228 +9,91 @@
|
||||
// coverage:ignore-file
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:auto_route/auto_route.dart' as _i12;
|
||||
import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i2;
|
||||
import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i6;
|
||||
import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i7;
|
||||
import 'package:auto_route/auto_route.dart' as _i6;
|
||||
import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i1;
|
||||
import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i3;
|
||||
import 'package:enaklo/presentation/pages/auth/register/register_page.dart'
|
||||
as _i9;
|
||||
import 'package:enaklo/presentation/pages/main/main_page.dart' as _i3;
|
||||
import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart'
|
||||
as _i1;
|
||||
import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart'
|
||||
as _i5;
|
||||
import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart'
|
||||
as _i8;
|
||||
import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart'
|
||||
as _i11;
|
||||
import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart'
|
||||
as _i4;
|
||||
import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i10;
|
||||
import 'package:flutter/material.dart' as _i13;
|
||||
import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart'
|
||||
as _i2;
|
||||
import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i5;
|
||||
|
||||
/// generated route for
|
||||
/// [_i1.HomePage]
|
||||
class HomeRoute extends _i12.PageRouteInfo<void> {
|
||||
const HomeRoute({List<_i12.PageRouteInfo>? children})
|
||||
: super(HomeRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'HomeRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i1.HomePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i2.LoginPage]
|
||||
class LoginRoute extends _i12.PageRouteInfo<void> {
|
||||
const LoginRoute({List<_i12.PageRouteInfo>? children})
|
||||
/// [_i1.LoginPage]
|
||||
class LoginRoute extends _i6.PageRouteInfo<void> {
|
||||
const LoginRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(LoginRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'LoginRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i2.LoginPage();
|
||||
return const _i1.LoginPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i3.MainPage]
|
||||
class MainRoute extends _i12.PageRouteInfo<void> {
|
||||
const MainRoute({List<_i12.PageRouteInfo>? children})
|
||||
: super(MainRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'MainRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i3.MainPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i4.OnboardingPage]
|
||||
class OnboardingRoute extends _i12.PageRouteInfo<void> {
|
||||
const OnboardingRoute({List<_i12.PageRouteInfo>? children})
|
||||
/// [_i2.OnboardingPage]
|
||||
class OnboardingRoute extends _i6.PageRouteInfo<void> {
|
||||
const OnboardingRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(OnboardingRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'OnboardingRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i4.OnboardingPage();
|
||||
return const _i2.OnboardingPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i5.OrderPage]
|
||||
class OrderRoute extends _i12.PageRouteInfo<void> {
|
||||
const OrderRoute({List<_i12.PageRouteInfo>? children})
|
||||
: super(OrderRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'OrderRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i5.OrderPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i6.OtpPage]
|
||||
class OtpRoute extends _i12.PageRouteInfo<void> {
|
||||
const OtpRoute({List<_i12.PageRouteInfo>? children})
|
||||
/// [_i3.OtpPage]
|
||||
class OtpRoute extends _i6.PageRouteInfo<void> {
|
||||
const OtpRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(OtpRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'OtpRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i6.OtpPage();
|
||||
return const _i3.OtpPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i7.PinPage]
|
||||
class PinRoute extends _i12.PageRouteInfo<PinRouteArgs> {
|
||||
PinRoute({
|
||||
_i13.Key? key,
|
||||
bool isCreatePin = true,
|
||||
String? title,
|
||||
List<_i12.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
PinRoute.name,
|
||||
args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'PinRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<PinRouteArgs>(
|
||||
orElse: () => const PinRouteArgs(),
|
||||
);
|
||||
return _i7.PinPage(
|
||||
key: args.key,
|
||||
isCreatePin: args.isCreatePin,
|
||||
title: args.title,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class PinRouteArgs {
|
||||
const PinRouteArgs({this.key, this.isCreatePin = true, this.title});
|
||||
|
||||
final _i13.Key? key;
|
||||
|
||||
final bool isCreatePin;
|
||||
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PinRouteArgs{key: $key, isCreatePin: $isCreatePin, title: $title}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i8.ProfilePage]
|
||||
class ProfileRoute extends _i12.PageRouteInfo<void> {
|
||||
const ProfileRoute({List<_i12.PageRouteInfo>? children})
|
||||
: super(ProfileRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProfileRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i8.ProfilePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i9.RegisterPage]
|
||||
class RegisterRoute extends _i12.PageRouteInfo<void> {
|
||||
const RegisterRoute({List<_i12.PageRouteInfo>? children})
|
||||
/// [_i4.RegisterPage]
|
||||
class RegisterRoute extends _i6.PageRouteInfo<void> {
|
||||
const RegisterRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(RegisterRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'RegisterRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i9.RegisterPage();
|
||||
return const _i4.RegisterPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i10.SplashPage]
|
||||
class SplashRoute extends _i12.PageRouteInfo<void> {
|
||||
const SplashRoute({List<_i12.PageRouteInfo>? children})
|
||||
/// [_i5.SplashPage]
|
||||
class SplashRoute extends _i6.PageRouteInfo<void> {
|
||||
const SplashRoute({List<_i6.PageRouteInfo>? children})
|
||||
: super(SplashRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'SplashRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
static _i6.PageInfo page = _i6.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i10.SplashPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i11.VoucherPage]
|
||||
class VoucherRoute extends _i12.PageRouteInfo<void> {
|
||||
const VoucherRoute({List<_i12.PageRouteInfo>? children})
|
||||
: super(VoucherRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'VoucherRoute';
|
||||
|
||||
static _i12.PageInfo page = _i12.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i11.VoucherPage();
|
||||
return const _i5.SplashPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,10 +6,6 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@ -8,11 +8,9 @@ import Foundation
|
||||
import connectivity_plus
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
64
pubspec.lock
64
pubspec.lock
@ -933,70 +933,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.18"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -28,7 +28,6 @@ dependencies:
|
||||
json_annotation: ^4.9.0
|
||||
shared_preferences: ^2.5.3
|
||||
carousel_slider: ^5.1.1
|
||||
url_launcher: ^6.3.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@ -7,11 +7,8 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user