first commit

This commit is contained in:
Anbarasu
2026-05-26 18:01:57 +05:30
commit 6d59c8daf6
297 changed files with 35238 additions and 0 deletions

View File

@@ -0,0 +1,882 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:math' as math;
import '../../constants/color_constants.dart';
import '../../constants/font_constants.dart';
import '../../widgets/text_widget.dart';
class OrderDetailsPage extends StatefulWidget {
final String orderId;
final String gstno;
final String storeName;
final String storeLocation;
final List<Map<String, dynamic>> items;
final double tax;
final double fee;
const OrderDetailsPage({
super.key,
required this.orderId,
required this.gstno,
required this.storeName,
required this.storeLocation,
required this.items,
required this.tax,
required this.fee,
});
@override
State<OrderDetailsPage> createState() => _OrderDetailsPageState();
}
class _OrderDetailsPageState extends State<OrderDetailsPage> with TickerProviderStateMixin {
late AnimationController _pageController;
late AnimationController _storeCardController;
late AnimationController _itemsController;
late AnimationController _billController;
late AnimationController _statusController;
late Animation<double> _pageFadeAnimation;
late Animation<Offset> _pageSlideAnimation;
late Animation<double> _storeScaleAnimation;
late Animation<double> _storeRotateAnimation;
late Animation<double> _itemsFadeAnimation;
late Animation<Offset> _itemsSlideAnimation;
late Animation<double> _billFadeAnimation;
late Animation<Offset> _billSlideAnimation;
late Animation<double> _statusFadeAnimation;
late Animation<double> _statusScaleAnimation;
@override
void initState() {
super.initState();
// Page animation
_pageController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_pageFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _pageController, curve: Curves.easeOut),
);
_pageSlideAnimation = Tween<Offset>(
begin: const Offset(0, 0.03),
end: Offset.zero,
).animate(CurvedAnimation(parent: _pageController, curve: Curves.easeOutCubic));
// Store card animation
_storeCardController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_storeScaleAnimation = Tween<double>(begin: 0.9, end: 1.0).animate(
CurvedAnimation(parent: _storeCardController, curve: Curves.easeOutBack),
);
_storeRotateAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _storeCardController, curve: Curves.easeOut),
);
// Items card animation
_itemsController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_itemsFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _itemsController, curve: Curves.easeOut),
);
_itemsSlideAnimation = Tween<Offset>(
begin: const Offset(0.05, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: _itemsController, curve: Curves.easeOutCubic));
// Bill summary animation
_billController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_billFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _billController, curve: Curves.easeOut),
);
_billSlideAnimation = Tween<Offset>(
begin: const Offset(-0.05, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: _billController, curve: Curves.easeOutCubic));
// Status animation
_statusController = AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
);
_statusFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _statusController, curve: Curves.easeOut),
);
_statusScaleAnimation = Tween<double>(begin: 0.85, end: 1.0).animate(
CurvedAnimation(parent: _statusController, curve: Curves.easeOutBack),
);
// Start animations in sequence
_startAnimations();
}
void _startAnimations() async {
_pageController.forward();
await Future.delayed(const Duration(milliseconds: 100));
_storeCardController.forward();
await Future.delayed(const Duration(milliseconds: 200));
_itemsController.forward();
await Future.delayed(const Duration(milliseconds: 150));
_billController.forward();
await Future.delayed(const Duration(milliseconds: 150));
_statusController.forward();
}
@override
void dispose() {
_pageController.dispose();
_storeCardController.dispose();
_itemsController.dispose();
_billController.dispose();
_statusController.dispose();
super.dispose();
}
double rs(BuildContext context, double size) {
final width = MediaQuery.of(context).size.width;
if (width > 600) {
return size * 1.2; // Scale up for tablets
}
return size;
}
@override
Widget build(BuildContext context) {
final total = widget.items.fold<double>(
0.0,
(sum, item) => sum + (double.tryParse(item['productSumPrice'].toString()) ?? 0.0),
);
final grandTotal = total + widget.tax + widget.fee;
return SafeArea(
top: false,
child: LayoutBuilder(
builder: (context, constraints) {
final isTablet = constraints.maxWidth > 600;
final padding = isTablet ? 32.0 : 16.0;
final maxWidth = isTablet ? 800.0 : double.infinity;
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: PreferredSize(
preferredSize: Size.fromHeight(isTablet ? 70 : kToolbarHeight),
child: FadeTransition(
opacity: _pageFadeAnimation,
child: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: Container(
margin: EdgeInsets.all(isTablet ? 12 : 8),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
borderRadius: BorderRadius.circular(isTablet ? 16 : 12),
),
child: IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: Icon(
Icons.arrow_back_ios_new_rounded,
size: isTablet ? 22 : 18,
color: ColorConstants.primaryColor,
),
),
),
centerTitle: true,
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
padding: EdgeInsets.all(isTablet ? 8 : 6),
decoration: BoxDecoration(
color: ColorConstants.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(isTablet ? 12 : 8),
),
child: Icon(
Icons.receipt_long_rounded,
color: ColorConstants.primaryColor,
size: isTablet ? 24 : 18,
),
),
);
},
),
SizedBox(width: isTablet ? 14 : 10),
ReusableTextWidget(
text: 'Order #${widget.orderId}',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 20 : 16),
fontWeight: FontWeight.w600,
textAlign: TextAlign.center,
),
],
),
),
),
),
body: FadeTransition(
opacity: _pageFadeAnimation,
child: SlideTransition(
position: _pageSlideAnimation,
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: SingleChildScrollView(
padding: EdgeInsets.all(padding),
physics: const BouncingScrollPhysics(),
child: Column(
children: [
// Store Information Card
_buildStoreCard(isTablet),
SizedBox(height: isTablet ? 28 : 20),
// Order Items Card
_buildItemsCard(isTablet),
SizedBox(height: isTablet ? 28 : 20),
// Bill Summary Card
_buildBillSummary(isTablet, total, grandTotal),
SizedBox(height: isTablet ? 24 : 16),
// Order Status
_buildOrderStatus(isTablet),
SizedBox(height: isTablet ? 32 : 20),
],
),
),
),
),
),
),
);
},
),
);
}
Widget _buildStoreCard(bool isTablet) {
return ScaleTransition(
scale: _storeScaleAnimation,
child: FadeTransition(
opacity: _storeScaleAnimation,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(isTablet ? 20 : 16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: isTablet ? 15 : 10,
offset: Offset(0, isTablet ? 3 : 2),
),
],
),
child: Column(
children: [
// Store Header
Container(
padding: EdgeInsets.all(isTablet ? 28 : 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isTablet ? 20 : 16),
topRight: Radius.circular(isTablet ? 20 : 16),
),
),
child: Row(
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 2 * math.pi),
duration: const Duration(milliseconds: 1000),
curve: Curves.easeOut,
builder: (context, value, child) {
return Transform.rotate(
angle: value,
child: Container(
padding: EdgeInsets.all(isTablet ? 16 : 12),
decoration: BoxDecoration(
color: ColorConstants.primaryColor,
borderRadius: BorderRadius.circular(isTablet ? 16 : 12),
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor.withOpacity(0.2),
blurRadius: isTablet ? 12 : 8,
offset: Offset(0, isTablet ? 6 : 4),
),
],
),
child: Icon(
Icons.storefront_rounded,
color: Colors.white,
size: isTablet ? 28 : 20,
),
),
);
},
),
SizedBox(width: isTablet ? 20 : 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: ReusableTextWidget(
text: widget.storeName,
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 20 : 16),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
);
},
),
SizedBox(height: isTablet ? 6 : 4),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 1000),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Row(
children: [
Icon(
Icons.location_on_outlined,
size: isTablet ? 16 : 12,
color: const Color(0xFF6B7280),
),
SizedBox(width: isTablet ? 6 : 4),
Flexible(
child: ReusableTextWidget(
text: widget.storeLocation,
color: const Color(0xFF6B7280),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 14 : 12),
fontWeight: FontWeight.w400,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
},
),
],
),
),
],
),
),
// GST Information with slide animation
TweenAnimationBuilder<Offset>(
tween: Tween(begin: const Offset(-0.1, 0), end: Offset.zero),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutCubic,
builder: (context, offset, child) {
return Transform.translate(
offset: Offset(offset.dx * 100, 0),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: isTablet ? 28 : 20,
vertical: isTablet ? 20 : 16,
),
child: Row(
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Icon(
Icons.verified_user_outlined,
size: isTablet ? 22 : 16,
color: ColorConstants.primaryColor,
),
);
},
),
SizedBox(width: isTablet ? 16 : 12),
ReusableTextWidget(
text: 'GST Number',
color: const Color(0xFF6B7280),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 14 : 12),
fontWeight: FontWeight.w500,
textAlign: TextAlign.start,
),
const Spacer(),
ReusableTextWidget(
text: widget.gstno,
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 15 : 13),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
],
),
),
);
},
),
],
),
),
),
);
}
Widget _buildItemsCard(bool isTablet) {
return FadeTransition(
opacity: _itemsFadeAnimation,
child: SlideTransition(
position: _itemsSlideAnimation,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(isTablet ? 20 : 16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: isTablet ? 15 : 10,
offset: Offset(0, isTablet ? 3 : 2),
),
],
),
child: Column(
children: [
// Header
Container(
padding: EdgeInsets.all(isTablet ? 28 : 20),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: const Color(0xFFE5E7EB), width: 1),
),
),
child: Row(
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Icon(
Icons.shopping_bag_outlined,
color: ColorConstants.primaryColor,
size: isTablet ? 24 : 18,
),
);
},
),
SizedBox(width: isTablet ? 16 : 12),
ReusableTextWidget(
text: 'Order Items',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 18 : 15),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
const Spacer(),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: isTablet ? 14 : 10,
vertical: isTablet ? 6 : 4,
),
decoration: BoxDecoration(
color: ColorConstants.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(isTablet ? 10 : 8),
),
child: ReusableTextWidget(
text: '${widget.items.length} ${widget.items.length == 1 ? 'item' : 'items'}',
color: ColorConstants.primaryColor,
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 14 : 12),
fontWeight: FontWeight.w600,
textAlign: TextAlign.center,
),
),
);
},
),
],
),
),
// Items List with staggered animation
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.items.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: const Color(0xFFE5E7EB),
indent: isTablet ? 28 : 20,
endIndent: isTablet ? 28 : 20,
),
itemBuilder: (context, index) {
final item = widget.items[index];
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 400 + (index * 100)),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return Transform.translate(
offset: Offset(30 * (1 - value), 0),
child: Opacity(
opacity: value,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: isTablet ? 28 : 20,
vertical: isTablet ? 20 : 16,
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: item['name'] ?? '',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 16 : 14),
fontWeight: FontWeight.w500,
textAlign: TextAlign.start,
),
SizedBox(height: isTablet ? 6 : 4),
ReusableTextWidget(
text: 'Qty: ${item['quantity']}',
color: const Color(0xFF6B7280),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 14 : 12),
fontWeight: FontWeight.w400,
textAlign: TextAlign.start,
),
],
),
),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 600 + (index * 100)),
curve: Curves.easeOut,
builder: (context, priceValue, child) {
return Opacity(
opacity: priceValue,
child: ReusableTextWidget(
text: '${(item['price'] ?? 0).toStringAsFixed(2)}',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 18 : 15),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
);
},
),
],
),
),
),
);
},
);
},
),
],
),
),
),
);
}
Widget _buildBillSummary(bool isTablet, double total, double grandTotal) {
return FadeTransition(
opacity: _billFadeAnimation,
child: SlideTransition(
position: _billSlideAnimation,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(isTablet ? 20 : 16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: isTablet ? 15 : 10,
offset: Offset(0, isTablet ? 3 : 2),
),
],
),
child: Column(
children: [
// Header
Container(
padding: EdgeInsets.all(isTablet ? 28 : 20),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: const Color(0xFFE5E7EB), width: 1),
),
),
child: Row(
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Icon(
Icons.receipt_outlined,
color: ColorConstants.primaryColor,
size: isTablet ? 24 : 18,
),
);
},
),
SizedBox(width: isTablet ? 16 : 12),
ReusableTextWidget(
text: 'Bill Summary',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 18 : 15),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
],
),
),
Padding(
padding: EdgeInsets.all(isTablet ? 28 : 20),
child: Column(
children: [
_buildAnimatedBillRow('Subtotal', total, isTablet, 0),
SizedBox(height: isTablet ? 16 : 12),
_buildAnimatedBillRow('GST', widget.tax, isTablet, 100),
SizedBox(height: isTablet ? 16 : 12),
_buildAnimatedBillRow('Delivery Fee', widget.fee, isTablet, 200),
SizedBox(height: isTablet ? 20 : 16),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Divider(height: 1, color: const Color(0xFFE5E7EB)),
);
},
),
SizedBox(height: isTablet ? 20 : 16),
// Grand Total with pulse animation
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.92, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ReusableTextWidget(
text: 'Grand Total',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 19 : 16),
fontWeight: FontWeight.w700,
textAlign: TextAlign.start,
),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: grandTotal),
duration: const Duration(milliseconds: 1200),
curve: Curves.easeOut,
builder: (context, animatedValue, child) {
return ReusableTextWidget(
text: '${animatedValue.toStringAsFixed(2)}',
color: ColorConstants.primaryColor,
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 22 : 18),
fontWeight: FontWeight.w700,
textAlign: TextAlign.start,
);
},
),
],
),
);
},
),
],
),
),
],
),
),
),
);
}
Widget _buildAnimatedBillRow(String label, double amount, bool isTablet, int delay) {
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 500 + delay),
curve: Curves.easeOut,
builder: (context, value, child) {
return Transform.translate(
offset: Offset(-20 * (1 - value), 0),
child: Opacity(
opacity: value,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ReusableTextWidget(
text: label,
color: const Color(0xFF6B7280),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 15 : 13),
fontWeight: FontWeight.w400,
textAlign: TextAlign.start,
),
ReusableTextWidget(
text: '${amount.toStringAsFixed(2)}',
color: const Color(0xFF1A1A1A),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 16 : 14),
fontWeight: FontWeight.w500,
textAlign: TextAlign.start,
),
],
),
),
);
},
);
}
Widget _buildOrderStatus(bool isTablet) {
return FadeTransition(
opacity: _statusFadeAnimation,
child: ScaleTransition(
scale: _statusScaleAnimation,
child: Container(
padding: EdgeInsets.all(isTablet ? 28 : 20),
decoration: BoxDecoration(
color: const Color(0xFFECFDF5),
borderRadius: BorderRadius.circular(isTablet ? 20 : 16),
border: Border.all(
color: const Color(0xFF10B981),
width: 1,
),
),
child: Row(
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutBack,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
padding: EdgeInsets.all(isTablet ? 14 : 10),
decoration: BoxDecoration(
color: const Color(0xFF10B981),
borderRadius: BorderRadius.circular(isTablet ? 14 : 10),
),
child: Icon(
Icons.check_rounded,
color: Colors.white,
size: isTablet ? 24 : 20,
),
),
);
},
),
SizedBox(width: isTablet ? 20 : 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ReusableTextWidget(
text: 'We appreciate your order!',
color: const Color(0xFF1F2937),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 18 : 15),
fontWeight: FontWeight.w600,
textAlign: TextAlign.start,
),
SizedBox(height: isTablet ? 6 : 4),
ReusableTextWidget(
text: 'Our team is taking care of it.',
color: const Color(0xFF6B7280),
fontFamily: FontConstants.fontFamily,
fontSize: rs(context, isTablet ? 14 : 12),
fontWeight: FontWeight.w400,
textAlign: TextAlign.start,
),
],
),
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,266 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import 'package:nearledaily/constants/color_constants.dart';
import '../../../constants/font_constants.dart';
import '../../controllers/cart_controller/cart.dart';
import '../../controllers/dashboard_controller/dashboard_controller.dart';
import '../../widgets/text_widget.dart';
import '../home_view.dart';
class OrderSuccessView extends StatefulWidget {
const OrderSuccessView({super.key});
@override
State<OrderSuccessView> createState() => _OrderSuccessViewState();
}
class _OrderSuccessViewState extends State<OrderSuccessView>
with TickerProviderStateMixin {
late AnimationController _fadeController;
late AnimationController _slideController;
late AnimationController _pulseController;
late Animation<double> _fadeAnim;
late Animation<Offset> _slideAnim;
late Animation<double> _pulseAnim;
@override
void initState() {
super.initState();
_fadeController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
);
_slideController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 700),
);
_pulseController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
)..repeat(reverse: true);
_fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeIn);
_slideAnim = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOut));
_pulseAnim = Tween<double>(begin: 1.0, end: 1.06).animate(
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
);
Future.delayed(const Duration(milliseconds: 200), () {
_fadeController.forward();
_slideController.forward();
});
}
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
_pulseController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final DashboardController controller = Get.put(DashboardController());
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) Get.back();
},
child: Scaffold(
backgroundColor: const Color(0xFFF6FBF4),
body: Stack(
children: [
// Decorative background blobs
Positioned(
top: -60,
right: -60,
child: Container(
width: 220,
height: 220,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorConstants.primaryColor.withOpacity(0.08),
),
),
),
Positioned(
bottom: 120,
left: -80,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorConstants.primaryColor.withOpacity(0.06),
),
),
),
SafeArea(
child: FadeTransition(
opacity: _fadeAnim,
child: SlideTransition(
position: _slideAnim,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: size.width * 0.06),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.05),
// Lottie animation with soft card bg
Container(
width: size.width * 0.70,
height: size.width * 0.70,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: ColorConstants.primaryColor.withOpacity(0.15),
blurRadius: 40,
spreadRadius: 8,
),
],
),
child: Lottie.asset(
repeat: false,
'assets/images/orderSuccess.json',
fit: BoxFit.contain,
),
),
SizedBox(height: size.height * 0.04),
// Headline
ReusableTextWidget(
text: 'Order Placed! 🎉',
color: const Color(0xFF1A2E1A),
fontFamily: FontConstants.fontFamily,
fontSize: 28,
fontWeight: FontWeight.w800,
textAlign: TextAlign.center,
),
SizedBox(height: size.height * 0.012),
// Subtitle
ReusableTextWidget(
text: "Your order is confirmed and\nbeing processed right now.",
color: const Color(0xFF6B7C6B),
fontFamily: FontConstants.fontFamily,
fontSize: 16,
fontWeight: FontWeight.w400,
maxLines: 3,
textAlign: TextAlign.center,
),
SizedBox(height: size.height * 0.04),
// Status chips row
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_StatusChip(icon: Icons.check_circle_rounded, label: 'Confirmed', color: ColorConstants.primaryColor),
const SizedBox(width: 10),
_StatusChip(icon: Icons.inventory_2_rounded, label: 'Packing', color: Colors.orange),
const SizedBox(width: 10),
_StatusChip(icon: Icons.local_shipping_rounded, label: 'On the way', color: Colors.blueAccent),
],
),
],
),
),
),
),
),
],
),
bottomNavigationBar: SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(
size.width * 0.06,
0,
size.width * 0.06,
size.height * 0.02,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Primary CTA
ScaleTransition(
scale: _pulseAnim,
child: SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: () {
controller.show.value = false;
final cartCtrl = Get.find<CartController>();
cartCtrl.appliedCoupon.value = "";
cartCtrl.amt.value = "";
Get.offAll(BottomNavigation());
},
style: ElevatedButton.styleFrom(
backgroundColor: ColorConstants.primaryColor,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: ReusableTextWidget(
text: 'Back to Home',
color: Colors.white,
fontFamily: FontConstants.fontFamily,
fontSize: 17,
fontWeight: FontWeight.w600,
textAlign: TextAlign.center,
),
),
),
),
SizedBox(height: size.height * 0.015),
],
),
),
),
),
);
}
}
// Small reusable status chip widget
class _StatusChip extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
const _StatusChip({
required this.icon,
required this.label,
required this.color,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: SizedBox(),
);
}
}

View File

@@ -0,0 +1,744 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:shimmer/shimmer.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../constants/asset_constants.dart';
import '../../constants/color_constants.dart';
import '../../constants/font_constants.dart';
import '../../controllers/tenant/get_tenant.dart'; // OrderedTenantController
import '../../widgets/text_widget.dart';
import 'my_orders.dart'; // OrderDatum
class OrdersByStoreScreen extends StatefulWidget {
final bool showBackArrow;
const OrdersByStoreScreen({super.key, required this.showBackArrow});
@override
_OrdersByStoreScreenState createState() => _OrdersByStoreScreenState();
}
class _OrdersByStoreScreenState extends State<OrdersByStoreScreen>
with SingleTickerProviderStateMixin {
final OrderedTenantController tenantController =
Get.put(OrderedTenantController());
final ScrollController _scrollController = ScrollController();
static const Color primaryColor = Color(0xFF662582);
int? _expandedIndex; // ✅ track which tile is expanded
late AnimationController _fabAnimationController;
late Animation<double> _fabAnimation;
final List<String> emojis = ['😡', '😕', '😐', '😊', '😍'];
Color _getStatusColor(String? status) {
final cleanStatus = status?.trim().toLowerCase() ?? '';
switch (cleanStatus) {
case 'created':
return Colors.blue;
case 'pending':
return Colors.orange;
case 'cancelled':
return Colors.red;
case 'completed':
return Colors.green;
default:
return Colors.grey;
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
tenantController
.refreshOrders(); // ✅ auto refresh every time this screen rebuilds
}
@override
void initState() {
super.initState();
// Initialize FAB animation
_fabAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_fabAnimation = CurvedAnimation(
parent: _fabAnimationController,
curve: Curves.easeInOut,
);
// Load initial orders
tenantController.loadOrders();
// Listen for scroll to bottom
_scrollController.addListener(() {
// FAB animation based on scroll
if (_scrollController.offset > 100) {
_fabAnimationController.forward();
} else {
_fabAnimationController.reverse();
}
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200 && // near bottom
!tenantController.isLoading.value) {
tenantController.pageNo++; // increment page
tenantController.loadOrders();
}
});
}
@override
void dispose() {
_scrollController.dispose();
_fabAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: Colors.grey[50],
body: CustomScrollView(
controller: _scrollController,
physics: const BouncingScrollPhysics(), // ✨ Smooth bouncing scroll
slivers: [
SliverAppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
// 🔥 Prevent color overlay when scrolled
scrolledUnderElevation: 0,
floating: false,
pinned: true,
// 👈 use widget.showBackArrow
automaticallyImplyLeading: widget.showBackArrow,
leading: widget.showBackArrow
? IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.pop(context),
splashRadius: 24, // ✨ Better ripple effect
)
: null,
title: const Text(
'Orders',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
// titleSpacing: -5,
centerTitle: false,
elevation: 0,
),
const SliverToBoxAdapter(child: SizedBox(height: 8)),
Obx(() {
if (tenantController.isLoading.value &&
tenantController.orders.isEmpty) {
// Initial loading
return SliverFillRemaining(
hasScrollBody: false,
child: shimmerListView(),
);
}
if (tenantController.orders.isEmpty) {
final screenSize = MediaQuery.of(context).size;
return SliverToBoxAdapter(
child: emptyOrdersWidget(screenSize),
);
}
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == tenantController.orders.length) {
// Loader at bottom
return tenantController.isLoading.value
? const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(child: CircularProgressIndicator()),
)
: const SizedBox.shrink();
}
final order = tenantController.orders[index];
final tenantName = order.tenantname ?? 'Unknown Tenant';
double totalAmount = 0.0;
if (order.orderdetails != null &&
order.orderdetails!.isNotEmpty) {
totalAmount = order.orderdetails!
.map((item) => item.productsumprice ?? 0.0)
.reduce((a, b) => a + b);
}
// ✨ Staggered fade-in animation for each item
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 300 + (index * 50)),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: Opacity(
opacity: value,
child: child,
),
);
},
child: Builder(builder: (tileContext) {
return Container(
margin: const EdgeInsets.symmetric(
vertical: 4, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.black12, width: 0.45),
borderRadius: BorderRadius.circular(8),
),
child: Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
key: ValueKey(
'${order.orderid}-${_expandedIndex == index}'),
//initiallyExpanded: _expandedIndex == index,
initiallyExpanded: _expandedIndex == index,
title: ReusableTextWidget(
text: tenantName,
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 15,
fontFamily: FontConstants.fontFamily,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// ● Circle dot
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: _getStatusColor(order.orderstatus),
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
Text(
(order.orderstatus ?? 'Pending')
.capitalizeFirst!,
style: const TextStyle(
color: Colors.black,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
],
),
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.location_on,
size: 13, color: Colors.grey),
const SizedBox(width: 2),
ReusableTextWidget(
text: order.tenantsuburb ?? 'Unknown Location',
color: Colors.grey[700]!,
fontWeight: FontWeight.w400,
fontSize: 10,
fontFamily: FontConstants.fontFamily,
),
],
),
leading: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.grey[200],
),
child: (order.tenantimage != null &&
order.tenantimage!.isNotEmpty)
? ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
order.tenantimage!,
fit: BoxFit.cover,
errorBuilder:
(context, error, stackTrace) {
return Icon(Icons.store,
size: 28,
color: Colors.grey[700]);
},
),
)
: Icon(Icons.store,
size: 28, color: Colors.grey[700]),
),
// ✅ this callback runs when the tile is expanded or collapsed
onExpansionChanged: (expanded) {
setState(() {
_expandedIndex = expanded ? index : null;
});
if (expanded) {
// ✨ Haptic feedback
HapticFeedback.selectionClick();
// ✨ Smooth scroll to expanded item
Future.delayed(const Duration(milliseconds: 200),
() {
Scrollable.ensureVisible(
tileContext,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
alignment: 0.1,
);
});
}
},
children: [
// ✨ Animated container for smooth expansion
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Padding(
padding: const EdgeInsets.only(top: 0, bottom: 8),
child: Column(
children: [
GestureDetector(
onTap: () {},
child: Container(
height: 180,
width: double.infinity,
margin: const EdgeInsets.symmetric(
vertical: 6, horizontal: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(9),
border: Border.all(
color: Colors.black12,
width: 0.20),
boxShadow: [
BoxShadow(
color: Colors.black
.withOpacity(0.04),
blurRadius: 4,
offset: const Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
ReusableTextWidget(
text:
"Order ID: ${order.orderid ?? 'Unknown'}",
color: ColorConstants
.blackColor
.withOpacity(0.87),
fontWeight: FontWeight.w600,
fontSize: 14,
fontFamily: FontConstants
.fontFamily,
),
Container(
padding:
const EdgeInsets
.symmetric(
horizontal: 8,
vertical: 4),
decoration: BoxDecoration(
color: Colors.purple
.withOpacity(0.2),
borderRadius:
BorderRadius.circular(
12),
),
child: ReusableTextWidget(
text:
"${order.orderdetails?.fold<int>(0, (sum, item) => sum + (item.orderqty ?? 0)) ?? 0}",
color: primaryColor,
fontWeight:
FontWeight.bold,
fontSize: 12,
fontFamily: FontConstants
.fontFamily,
),
),
],
),
const SizedBox(height: 6),
Row(
children: [
ReusableTextWidget(
text: "Total Amount: ",
color: ColorConstants
.blackColor
.withOpacity(0.65),
fontWeight: FontWeight.w600,
fontSize: 12,
fontFamily: FontConstants
.fontFamily,
),
ReusableTextWidget(
text:
"${totalAmount.toStringAsFixed(2)}",
color: ColorConstants
.blackColor
.withOpacity(0.67),
fontWeight: FontWeight.w600,
fontSize: 13,
fontFamily: FontConstants
.fontFamily,
),
],
),
const SizedBox(height: 6),
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
const SizedBox(width: 4),
ReusableTextWidget(
text: order.orderdate != null
? "${order.orderdate!.day} ${_getMonthName(order.orderdate!.month)} ${order.orderdate!.year}"
: 'No Date',
color: ColorConstants
.blackColor
.withOpacity(0.65),
fontWeight: FontWeight.w500,
fontSize: 12,
fontFamily: FontConstants
.fontFamily,
),
],
),
const SizedBox(height: 8),
Divider(color: Colors.grey[300]),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
GestureDetector(
onTap: () async {
final uri = Uri(scheme: 'tel', path: order.pickupcontactno!);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: 'Contact :',
color: ColorConstants.blackColor.withOpacity(0.67),
fontWeight: FontWeight.w600,
fontSize: 13,
fontFamily: FontConstants.fontFamily,
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.phone_rounded,
size: 14,
color: ColorConstants.primaryColor,
),
const SizedBox(width: 5),
ReusableTextWidget(
text: order.pickupcontactno ?? "No Contact",
color: ColorConstants.primaryColor,
fontWeight: FontWeight.w600,
fontSize: 13,
fontFamily: FontConstants.fontFamily,
),
],
),
],
),
),
ElevatedButton(
style: ElevatedButton
.styleFrom(
padding:
const EdgeInsets
.symmetric(
horizontal: 12,
vertical: 5),
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
8),
),
backgroundColor:
primaryColor,
),
onPressed: () {
// ✨ Haptic feedback on button press
HapticFeedback.mediumImpact();
// ✨ Smooth page transition
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context,
animation,
secondaryAnimation) =>
OrderDetailsPage(
orderId: order
.orderid ??
'Unknown',
storeName: tenantName,
storeLocation: order
.tenantsuburb ??
'Unknown',
tax: order
.totaltaxamount ??
0,
gstno: order.gstno ?? "",
fee: order
.deliverycharge ??
0,
items: order
.orderdetails
?.map((item) =>
{
'name':
item.productname ?? 'Unknown',
'quantity':
item.orderqty ?? 0,
'productSumPrice':
item.productsumprice ?? 0.0,
'price':
item.price ?? 0.0,
'discountamount':
item.price ?? 0.0,
'image':
item.productimage ?? '',
})
.toList() ??
[],
),
transitionsBuilder:
(context,
animation,
secondaryAnimation,
child) {
return FadeTransition(
opacity: animation,
child:
SlideTransition(
position:
Tween<Offset>(
begin:
const Offset(
0.05, 0),
end:
Offset.zero,
).animate(
animation),
child: child,
),
);
},
transitionDuration:
const Duration(
milliseconds:
300),
),
);
},
child: const Text(
"View Details",
style: TextStyle(
fontSize: 12,
color: Colors.white),
),
),
],
),
],
),
),
),
],
),
),
),
],
),
),
);
}),
);
},
childCount:
tenantController.orders.length + 1, // extra for loader
),
);
}),
],
),
// ✨ Floating Action Button for scroll to top
floatingActionButton: ScaleTransition(
scale: _fabAnimation,
child: FloatingActionButton.small(
backgroundColor: primaryColor,
elevation: 4,
onPressed: () {
HapticFeedback.mediumImpact();
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: const Icon(
Icons.arrow_upward,
color: Colors.white,
size: 20,
),
),
),
),
);
}
// Helper method to get month name
String _getMonthName(int month) {
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
];
return months[month - 1];
}
// Shimmer placeholder for initial loading
Widget shimmerListView() {
return Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Column(
children: List.generate(15, (index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0,horizontal: 8),
child: Container(
height: 80,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
);
}),
),
);
}
}
Widget emptyOrdersWidget(Size screenSize) {
// ✨ Animated empty state
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return Transform.scale(
scale: 0.8 + (0.2 * value),
child: Opacity(
opacity: value,
child: child,
),
);
},
child: Padding(
padding: EdgeInsets.only(
left: screenSize.width * 0.08,
right: screenSize.width * 0.08,
top: screenSize.height * 0.12,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: screenSize.height * 0.06),
Image.asset(
AssetConstants.noOrders,
height: screenSize.height * 0.25,
width: screenSize.width * 0.50,
fit: BoxFit.fill,
),
ReusableTextWidget(
text: 'No Orders Yet!',
color: ColorConstants.blackColor,
fontFamily: FontConstants.fontFamily,
fontSize: 18,
fontWeight: FontWeight.w700,
maxLines: 2,
textAlign: TextAlign.center,
),
SizedBox(height: screenSize.height * 0.01),
ReusableTextWidget(
text: 'Stay tuned, your next order will appear here soon!',
color: ColorConstants.blackColor,
fontFamily: FontConstants.fontFamily,
fontSize: 14,
fontWeight: FontWeight.normal,
maxLines: 2,
textAlign: TextAlign.center,
),
],
),
),
);
}