270 lines
10 KiB
Dart
Raw Normal View History

2025-08-12 17:36:41 +07:00
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
2025-08-18 14:24:15 +07:00
import 'package:flutter_bloc/flutter_bloc.dart';
2025-08-12 23:27:34 +07:00
import 'package:line_icons/line_icons.dart';
2025-08-12 17:36:41 +07:00
2025-08-18 14:24:15 +07:00
import '../../../application/order/order_loader/order_loader_bloc.dart';
2025-08-12 21:27:13 +07:00
import '../../../common/theme/theme.dart';
2025-08-18 14:24:15 +07:00
import '../../../injection.dart';
2025-08-16 00:39:09 +07:00
import '../../components/appbar/appbar.dart';
2025-08-12 23:27:34 +07:00
import '../../components/button/button.dart';
import '../../components/spacer/spacer.dart';
2025-08-18 14:24:15 +07:00
import '../../components/widgets/empty_widget.dart';
2025-08-12 23:27:34 +07:00
import 'widgets/status_tile.dart';
2025-08-18 13:33:45 +07:00
import 'widgets/order_tile.dart';
2025-08-12 21:27:13 +07:00
2025-08-12 17:36:41 +07:00
@RoutePage()
2025-08-18 14:24:15 +07:00
class OrderPage extends StatefulWidget implements AutoRouteWrapper {
2025-08-18 13:33:45 +07:00
const OrderPage({super.key});
2025-08-12 17:36:41 +07:00
2025-08-12 21:27:13 +07:00
@override
2025-08-18 13:33:45 +07:00
State<OrderPage> createState() => _OrderPageState();
2025-08-18 14:24:15 +07:00
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (_) =>
getIt<OrderLoaderBloc>()
..add(OrderLoaderEvent.fetched(isRefresh: true)),
child: this,
);
2025-08-12 21:27:13 +07:00
}
2025-08-18 13:33:45 +07:00
class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
2025-08-12 23:27:34 +07:00
late AnimationController _fadeController;
late AnimationController _slideController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
2025-08-18 14:24:15 +07:00
final ScrollController _scrollController = ScrollController();
2025-08-12 21:27:13 +07:00
2025-08-12 23:27:34 +07:00
// Filter state
String selectedFilter = 'All';
2025-08-18 14:24:15 +07:00
final List<String> filterOptions = ['All', 'Completed', 'Pending'];
2025-08-12 21:27:13 +07:00
2025-08-12 17:36:41 +07:00
@override
2025-08-12 23:27:34 +07:00
void initState() {
super.initState();
2025-08-12 21:27:13 +07:00
2025-08-12 23:27:34 +07:00
_fadeController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
2025-08-12 21:27:13 +07:00
);
2025-08-12 23:27:34 +07:00
_slideController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
2025-08-12 21:27:13 +07:00
);
2025-08-12 23:27:34 +07:00
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
2025-08-12 21:27:13 +07:00
);
2025-08-12 23:27:34 +07:00
_slideAnimation =
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
CurvedAnimation(parent: _slideController, curve: Curves.elasticOut),
);
2025-08-12 21:27:13 +07:00
2025-08-12 23:27:34 +07:00
_fadeController.forward();
_slideController.forward();
2025-08-12 21:27:13 +07:00
}
2025-08-12 23:27:34 +07:00
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
super.dispose();
2025-08-12 21:27:13 +07:00
}
2025-08-12 23:27:34 +07:00
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
2025-08-18 14:24:15 +07:00
body: BlocListener<OrderLoaderBloc, OrderLoaderState>(
listenWhen: (p, c) => p.status != c.status,
listener: (context, state) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(isRefresh: true),
);
},
child: BlocBuilder<OrderLoaderBloc, OrderLoaderState>(
builder: (context, state) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
_scrollController.position.extentAfter == 0) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(),
);
return true;
}
2025-08-12 21:27:13 +07:00
2025-08-18 14:24:15 +07:00
return true;
},
child: CustomScrollView(
controller: _scrollController,
slivers: [
// Custom App Bar with Hero Effect
SliverAppBar(
expandedHeight: 120,
floating: true,
pinned: true,
backgroundColor: AppColor.primary,
centerTitle: false,
flexibleSpace: CustomAppBar(title: 'Order', isBack: false),
actions: [
ActionIconButton(onTap: () {}, icon: LineIcons.filter),
SpaceWidth(8),
],
),
// Pinned Filter Section
SliverPersistentHeader(
pinned: true,
delegate: _FilterHeaderDelegate(
child: Container(
color: AppColor.background,
padding: EdgeInsets.fromLTRB(
AppValue.padding,
10,
AppValue.padding,
10,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: filterOptions.map((option) {
final index = filterOptions.indexOf(option);
return Padding(
padding: EdgeInsets.only(
right: index < filterOptions.length - 1
? 8
: 0,
),
child: OrderStatusTile(
label: option,
isSelected: option == selectedFilter,
onSelected: (isSelected) {
if (isSelected) {
setState(() {
selectedFilter = option;
});
if (option.toLowerCase() == 'all') {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.statusChanged(
'',
),
);
} else {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.statusChanged(
option.toLowerCase(),
),
);
}
}
},
),
);
}).toList(),
),
2025-08-12 23:27:34 +07:00
),
2025-08-18 14:24:15 +07:00
],
),
2025-08-12 21:27:13 +07:00
),
),
2025-08-18 14:24:15 +07:00
),
2025-08-12 21:27:13 +07:00
2025-08-18 14:24:15 +07:00
// Content
SliverPadding(
padding: EdgeInsets.all(AppValue.padding),
sliver: SliverList(
delegate: SliverChildListDelegate([
FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Column(
2025-08-12 23:27:34 +07:00
children: [
2025-08-18 14:24:15 +07:00
// Show filtered transaction count
if (selectedFilter != 'All')
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
children: [
Text(
'${state.orders.length} ${selectedFilter.toLowerCase()} order${state.orders.length != 1 ? 's' : ''}',
style: TextStyle(
color: AppColor.textSecondary,
fontSize: 14,
),
),
],
),
2025-08-12 23:27:34 +07:00
),
2025-08-18 14:24:15 +07:00
// Transaction List
state.orders.isEmpty
? EmptyWidget(
title: 'Order',
message:
'No ${selectedFilter.toLowerCase()} orders found',
)
: ListView.builder(
itemCount: state.orders.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
return OrderTile(
onTap: () {},
order: state.orders[index],
);
},
),
2025-08-12 23:27:34 +07:00
],
),
2025-08-12 21:27:13 +07:00
),
2025-08-18 14:24:15 +07:00
),
]),
2025-08-12 21:27:13 +07:00
),
),
2025-08-18 14:24:15 +07:00
],
),
);
},
),
2025-08-12 21:27:13 +07:00
),
);
2025-08-12 17:36:41 +07:00
}
}
2025-08-12 23:27:34 +07:00
// Custom delegate for pinned filter header
class _FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
_FilterHeaderDelegate({required this.child});
@override
double get minExtent => 70; // Minimum height when collapsed
@override
double get maxExtent => 70; // Maximum height when expanded
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return child;
}
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}