first commit
This commit is contained in:
882
lib/view/orders/my_orders.dart
Normal file
882
lib/view/orders/my_orders.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
266
lib/view/orders/order_succes.dart
Normal file
266
lib/view/orders/order_succes.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
744
lib/view/orders/orders_by_tenant.dart
Normal file
744
lib/view/orders/orders_by_tenant.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user