update pesanan
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/order/order_loader/order_loader_bloc.dart';
|
||||
import '../../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../injection.dart';
|
||||
@@ -23,10 +24,19 @@ class OrderPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
State<OrderPage> createState() => _OrderPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<OrderLoaderBloc>()
|
||||
..add(OrderLoaderEvent.fetched(isRefresh: true)),
|
||||
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<OrderLoaderBloc>()
|
||||
..add(OrderLoaderEvent.fetched(isRefresh: true)),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<OutletListLoaderBloc>()
|
||||
..add(const OutletListLoaderEvent.fetched()),
|
||||
),
|
||||
],
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
@@ -179,6 +189,14 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
backgroundColor: AppColor.background,
|
||||
body: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<OrderLoaderBloc, OrderLoaderState>(
|
||||
listenWhen: (p, c) => p.outletId != c.outletId,
|
||||
listener: (context, state) {
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.fetched(isRefresh: true),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocListener<OrderLoaderBloc, OrderLoaderState>(
|
||||
listenWhen: (p, c) => p.status != c.status,
|
||||
listener: (context, state) {
|
||||
@@ -198,137 +216,151 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
},
|
||||
),
|
||||
],
|
||||
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;
|
||||
}
|
||||
child: BlocBuilder<OutletListLoaderBloc, OutletListLoaderState>(
|
||||
builder: (context, outletListState) {
|
||||
return 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: context.lang.orders,
|
||||
isBack: false,
|
||||
),
|
||||
),
|
||||
|
||||
// Pinned Filter Section
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: FilterHeaderDelegate(
|
||||
backgroundColor: AppColor.background,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
AppValue.padding,
|
||||
10,
|
||||
AppValue.padding,
|
||||
10,
|
||||
),
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
filterOptions: filterOptions,
|
||||
selectedFilter: state.status,
|
||||
onDateChanged: (startDate, endDate) {
|
||||
if (startDate != null && endDate != null) {
|
||||
log('Date changed');
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.rangeDateChanged(
|
||||
startDate,
|
||||
endDate,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onFilterChanged: (filter) {
|
||||
final status = filter.toLowerCase();
|
||||
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.statusChanged(status),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 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 (state.status != 'all' && !state.isFetching)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${state.orders.length} ${state.status.toLowerCase()} ${context.lang.orders}',
|
||||
style: TextStyle(
|
||||
color: AppColor.textSecondary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Order List with Shimmer Loading
|
||||
if (state.isFetching)
|
||||
_buildShimmerList()
|
||||
else if (state.orders.isEmpty)
|
||||
EmptyWidget(
|
||||
title: context.lang.order,
|
||||
message: context.lang.no_order_with_status(
|
||||
state.status.toLowerCase(),
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.builder(
|
||||
itemCount: state.orders.length,
|
||||
shrinkWrap: true,
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (context, index) {
|
||||
return OrderTile(
|
||||
onTap: () => context.router.push(
|
||||
OrderDetailRoute(
|
||||
order: state.orders[index],
|
||||
),
|
||||
),
|
||||
order: state.orders[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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: context.lang.orders,
|
||||
isBack: false,
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
// Pinned Filter Section
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: FilterHeaderDelegate(
|
||||
backgroundColor: AppColor.background,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
AppValue.padding,
|
||||
10,
|
||||
AppValue.padding,
|
||||
10,
|
||||
),
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
filterOptions: filterOptions,
|
||||
selectedFilter: state.status,
|
||||
selectedOutletId: state.outletId,
|
||||
outletListState: outletListState,
|
||||
onDateChanged: (startDate, endDate) {
|
||||
if (startDate != null && endDate != null) {
|
||||
log('Date changed');
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.rangeDateChanged(
|
||||
startDate,
|
||||
endDate,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onFilterChanged: (filter) {
|
||||
final status = filter.toLowerCase();
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.statusChanged(status),
|
||||
);
|
||||
},
|
||||
onOutletChanged: (outletId) {
|
||||
context.read<OrderLoaderBloc>().add(
|
||||
OrderLoaderEvent.outletChanged(outletId),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 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 (state.status != 'all' &&
|
||||
!state.isFetching)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${state.orders.length} ${state.status.toLowerCase()} ${context.lang.orders}',
|
||||
style: TextStyle(
|
||||
color: AppColor.textSecondary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Order List with Shimmer Loading
|
||||
if (state.isFetching)
|
||||
_buildShimmerList()
|
||||
else if (state.orders.isEmpty)
|
||||
EmptyWidget(
|
||||
title: context.lang.order,
|
||||
message:
|
||||
context.lang.no_order_with_status(
|
||||
state.status.toLowerCase(),
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.builder(
|
||||
itemCount: state.orders.length,
|
||||
shrinkWrap: true,
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (context, index) {
|
||||
return OrderTile(
|
||||
onTap: () => context.router.push(
|
||||
OrderDetailRoute(
|
||||
order: state.orders[index],
|
||||
),
|
||||
),
|
||||
order: state.orders[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart';
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../../domain/outlet/outlet.dart';
|
||||
import '../../../../components/field/date_range_picker_field.dart';
|
||||
import 'status_tile.dart';
|
||||
|
||||
@@ -11,8 +14,11 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
final DateTime? endDate;
|
||||
final List<String> filterOptions;
|
||||
final String selectedFilter;
|
||||
final String? selectedOutletId;
|
||||
final OutletListLoaderState outletListState;
|
||||
final Function(DateTime?, DateTime?) onDateChanged;
|
||||
final Function(String) onFilterChanged;
|
||||
final Function(String?) onOutletChanged;
|
||||
|
||||
FilterHeaderDelegate({
|
||||
required this.backgroundColor,
|
||||
@@ -21,15 +27,18 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
required this.endDate,
|
||||
required this.filterOptions,
|
||||
required this.selectedFilter,
|
||||
required this.selectedOutletId,
|
||||
required this.outletListState,
|
||||
required this.onDateChanged,
|
||||
required this.onFilterChanged,
|
||||
required this.onOutletChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
double get minExtent => 130;
|
||||
double get minExtent => 190;
|
||||
|
||||
@override
|
||||
double get maxExtent => 130;
|
||||
double get maxExtent => 190;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
@@ -37,51 +46,55 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
final selectedOutlet = outletListState.outlets
|
||||
.where((o) => o.id == selectedOutletId)
|
||||
.firstOrNull;
|
||||
|
||||
return Container(
|
||||
color: backgroundColor,
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: DateRangePickerField(
|
||||
maxDate: DateTime.now(),
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
onChanged: (start, end) {
|
||||
onDateChanged(start, end);
|
||||
},
|
||||
),
|
||||
// Date range picker
|
||||
DateRangePickerField(
|
||||
maxDate: DateTime.now(),
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
onChanged: (start, end) => onDateChanged(start, end),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: filterOptions.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final option = entry.value;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: index < filterOptions.length - 1 ? 8 : 0,
|
||||
),
|
||||
child: OrderStatusTile(
|
||||
label: option == "All"
|
||||
? context.lang.all
|
||||
: option == "Completed"
|
||||
? context.lang.completed
|
||||
: context.lang.pending,
|
||||
isSelected: option == selectedFilter,
|
||||
onSelected: (isSelected) {
|
||||
if (isSelected) {
|
||||
onFilterChanged(option);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Outlet selector + status chips
|
||||
_OutletSelectorField(
|
||||
selectedOutlet: selectedOutlet,
|
||||
isLoading: outletListState.isFetching,
|
||||
onTap: () => _showOutletSheet(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Status chips
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: filterOptions.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final option = entry.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: index < filterOptions.length - 1 ? 8 : 0,
|
||||
),
|
||||
child: OrderStatusTile(
|
||||
label: option == 'All'
|
||||
? context.lang.all
|
||||
: option == 'Completed'
|
||||
? context.lang.completed
|
||||
: context.lang.pending,
|
||||
isSelected: option.toLowerCase() == selectedFilter,
|
||||
onSelected: (isSelected) {
|
||||
if (isSelected) onFilterChanged(option);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -89,12 +102,258 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
void _showOutletSheet(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (_) => _OutletBottomSheet(
|
||||
outlets: outletListState.outlets,
|
||||
selectedOutletId: selectedOutletId,
|
||||
onSelected: (outletId) {
|
||||
Navigator.pop(context);
|
||||
onOutletChanged(outletId);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant FilterHeaderDelegate oldDelegate) {
|
||||
return oldDelegate.startDate != startDate ||
|
||||
oldDelegate.endDate != endDate ||
|
||||
oldDelegate.selectedFilter != selectedFilter ||
|
||||
oldDelegate.selectedOutletId != selectedOutletId ||
|
||||
oldDelegate.outletListState.outlets.length !=
|
||||
outletListState.outlets.length ||
|
||||
oldDelegate.outletListState.isFetching != outletListState.isFetching ||
|
||||
oldDelegate.filterOptions.length != filterOptions.length ||
|
||||
oldDelegate.backgroundColor != backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Field outlet — ikut style DateRangePickerField
|
||||
class _OutletSelectorField extends StatefulWidget {
|
||||
final Outlet? selectedOutlet;
|
||||
final bool isLoading;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _OutletSelectorField({
|
||||
required this.selectedOutlet,
|
||||
required this.isLoading,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_OutletSelectorField> createState() => _OutletSelectorFieldState();
|
||||
}
|
||||
|
||||
class _OutletSelectorFieldState extends State<_OutletSelectorField> {
|
||||
bool _isPressed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasValue = widget.selectedOutlet != null;
|
||||
final label = widget.selectedOutlet?.name ?? 'Semua Outlet';
|
||||
|
||||
return GestureDetector(
|
||||
onTap: widget.isLoading ? null : widget.onTap,
|
||||
onTapDown: (_) => setState(() => _isPressed = true),
|
||||
onTapUp: (_) => setState(() => _isPressed = false),
|
||||
onTapCancel: () => setState(() => _isPressed = false),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
height: 52,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: _isPressed ? AppColor.backgroundLight : AppColor.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _isPressed ? AppColor.primary : AppColor.border,
|
||||
width: _isPressed ? 2 : 1,
|
||||
),
|
||||
boxShadow: _isPressed
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: hasValue ? FontWeight.w500 : FontWeight.w400,
|
||||
color: hasValue
|
||||
? AppColor.textPrimary
|
||||
: AppColor.textSecondary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: widget.isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.store_rounded,
|
||||
size: 20,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom sheet pilih outlet
|
||||
class _OutletBottomSheet extends StatelessWidget {
|
||||
final List<Outlet> outlets;
|
||||
final String? selectedOutletId;
|
||||
final Function(String?) onSelected;
|
||||
|
||||
const _OutletBottomSheet({
|
||||
required this.outlets,
|
||||
required this.selectedOutletId,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Pilih Outlet',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_OutletItem(
|
||||
label: 'Semua Outlet',
|
||||
icon: Icons.store_rounded,
|
||||
isSelected: selectedOutletId == null,
|
||||
onTap: () => onSelected(null),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
...outlets.map(
|
||||
(outlet) => Column(
|
||||
children: [
|
||||
_OutletItem(
|
||||
label: outlet.name,
|
||||
icon: Icons.storefront_rounded,
|
||||
isSelected: selectedOutletId == outlet.id,
|
||||
isActive: outlet.isActive,
|
||||
onTap: () => onSelected(outlet.id),
|
||||
),
|
||||
if (outlet != outlets.last) const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OutletItem extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final bool isSelected;
|
||||
final bool? isActive;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _OutletItem({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.isActive,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColor.primary.withOpacity(0.1)
|
||||
: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color: isSelected ? AppColor.primary : AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isSelected ? AppColor.primary : AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isActive != null)
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isActive! ? AppColor.success : AppColor.error,
|
||||
),
|
||||
),
|
||||
if (isActive != null) const SizedBox(width: 8),
|
||||
if (isSelected)
|
||||
const Icon(
|
||||
Icons.check_rounded,
|
||||
color: AppColor.primary,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user