270 lines
10 KiB
Dart
270 lines
10 KiB
Dart
import 'package:auto_route/auto_route.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:line_icons/line_icons.dart';
|
|
|
|
import '../../../application/order/order_loader/order_loader_bloc.dart';
|
|
import '../../../common/theme/theme.dart';
|
|
import '../../../injection.dart';
|
|
import '../../components/appbar/appbar.dart';
|
|
import '../../components/button/button.dart';
|
|
import '../../components/spacer/spacer.dart';
|
|
import '../../components/widgets/empty_widget.dart';
|
|
import 'widgets/status_tile.dart';
|
|
import 'widgets/order_tile.dart';
|
|
|
|
@RoutePage()
|
|
class OrderPage extends StatefulWidget implements AutoRouteWrapper {
|
|
const OrderPage({super.key});
|
|
|
|
@override
|
|
State<OrderPage> createState() => _OrderPageState();
|
|
|
|
@override
|
|
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
|
create: (_) =>
|
|
getIt<OrderLoaderBloc>()
|
|
..add(OrderLoaderEvent.fetched(isRefresh: true)),
|
|
child: this,
|
|
);
|
|
}
|
|
|
|
class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
|
late AnimationController _fadeController;
|
|
late AnimationController _slideController;
|
|
late Animation<double> _fadeAnimation;
|
|
late Animation<Offset> _slideAnimation;
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
// Filter state
|
|
String selectedFilter = 'All';
|
|
final List<String> filterOptions = ['All', 'Completed', 'Pending'];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_fadeController = AnimationController(
|
|
duration: const Duration(milliseconds: 800),
|
|
vsync: this,
|
|
);
|
|
|
|
_slideController = AnimationController(
|
|
duration: const Duration(milliseconds: 1000),
|
|
vsync: this,
|
|
);
|
|
|
|
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
|
|
);
|
|
|
|
_slideAnimation =
|
|
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
|
CurvedAnimation(parent: _slideController, curve: Curves.elasticOut),
|
|
);
|
|
|
|
_fadeController.forward();
|
|
_slideController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_fadeController.dispose();
|
|
_slideController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColor.background,
|
|
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;
|
|
}
|
|
|
|
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(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Content
|
|
SliverPadding(
|
|
padding: EdgeInsets.all(AppValue.padding),
|
|
sliver: SliverList(
|
|
delegate: SliverChildListDelegate([
|
|
FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: SlideTransition(
|
|
position: _slideAnimation,
|
|
child: Column(
|
|
children: [
|
|
// 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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 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],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|