e_receipt_mobile/lib/presentation/home/home_screen.dart

254 lines
9.0 KiB
Dart
Raw Normal View History

2026-02-14 10:16:13 +00:00
import 'package:e_receipt_mobile/domain/entities/login_user.dart';
2026-02-14 10:51:16 +00:00
import 'package:e_receipt_mobile/presentation/auth/session_controller.dart';
2026-03-30 09:40:06 +00:00
import 'package:e_receipt_mobile/presentation/home/home_pagination_providers.dart';
import 'package:e_receipt_mobile/presentation/home/merchant_paging_view_model.dart';
import 'package:e_receipt_mobile/presentation/home/widgets/home_drawer.dart';
import 'package:e_receipt_mobile/presentation/home/widgets/merchant_header.dart';
import 'package:e_receipt_mobile/presentation/home/widgets/merchant_list_view.dart';
2026-02-14 18:41:20 +00:00
import 'package:e_receipt_mobile/presentation/settings/settings_screen.dart';
2026-02-14 17:57:18 +00:00
import 'package:e_receipt_mobile/presentation/terminal/terminal_selection_screen.dart';
2026-02-13 19:46:02 +00:00
import 'package:flutter/material.dart';
2026-02-14 10:46:58 +00:00
import 'package:flutter_riverpod/flutter_riverpod.dart';
2026-02-13 19:46:02 +00:00
2026-02-14 10:46:58 +00:00
class HomeScreen extends ConsumerWidget {
2026-02-14 10:16:13 +00:00
const HomeScreen({required this.user, super.key});
final LoginUser user;
2026-02-13 19:46:02 +00:00
@override
2026-02-14 10:46:58 +00:00
Widget build(BuildContext context, WidgetRef ref) {
2026-03-30 09:40:06 +00:00
final pagingState = ref.watch(merchantPagingViewModelProvider);
final pagingViewModel = ref.read(merchantPagingViewModelProvider.notifier);
final colorScheme = Theme.of(context).colorScheme;
final query = ref.watch(merchantSearchQueryProvider);
2026-02-14 10:46:58 +00:00
2026-02-13 19:46:02 +00:00
return Scaffold(
appBar: AppBar(
2026-03-30 09:40:06 +00:00
title: const Text('Merchants'),
actions: [
IconButton(
tooltip: 'Refresh',
onPressed: pagingState.isLoading
? null
: () => pagingViewModel.refresh(),
icon: const Icon(Icons.refresh),
),
],
2026-02-13 19:46:02 +00:00
),
2026-03-30 09:40:06 +00:00
drawer: HomeDrawer(
user: user,
onProfile: () {
Navigator.of(context).pop();
_showProfile(context, user);
},
onReports: () {
Navigator.of(context).pop();
_showComingSoon(context, 'Reports');
},
onSettings: () {
Navigator.of(context).pop();
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const SettingsScreen()),
);
},
onHelp: () {
Navigator.of(context).pop();
_showComingSoon(context, 'Help');
},
onLogout: () async {
final shouldLogout = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Logout'),
content: const Text('Are you sure you want to logout?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
2026-02-14 10:51:16 +00:00
),
2026-03-30 09:40:06 +00:00
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Logout'),
2026-02-14 18:34:16 +00:00
),
2026-03-30 09:40:06 +00:00
],
);
},
);
2026-02-14 18:34:16 +00:00
2026-03-30 09:40:06 +00:00
if (shouldLogout != true || !context.mounted) {
return;
}
2026-02-14 18:34:16 +00:00
2026-03-30 09:40:06 +00:00
Navigator.of(context).pop(); // close drawer
ref.read(sessionControllerProvider.notifier).clearUser();
},
),
body: pagingState.isLoading && pagingState.items.isEmpty
? const Center(child: CircularProgressIndicator())
: pagingState.errorMessage != null && pagingState.items.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.fromLTRB(
24,
24,
24,
MediaQuery.viewPaddingOf(context).bottom + 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.wifi_off_outlined,
size: 56,
color: colorScheme.onSurfaceVariant,
2026-02-14 10:51:16 +00:00
),
2026-03-30 09:40:06 +00:00
const SizedBox(height: 12),
Text(
'Failed to load merchants',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
Text(
pagingState.errorMessage!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 14),
FilledButton.tonal(
onPressed: pagingViewModel.refresh,
child: const Text('Try again'),
),
],
),
2026-02-14 10:51:16 +00:00
),
2026-03-30 09:40:06 +00:00
)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
2026-02-14 10:46:58 +00:00
children: [
2026-03-30 09:40:06 +00:00
MerchantHeader(
loadedCount: pagingState.items.length,
query: query,
onQueryChanged: (value) {
ref.read(merchantSearchQueryProvider.notifier).state =
value;
pagingViewModel.setSearchTerm(value);
},
onClear: () {
ref.read(merchantSearchQueryProvider.notifier).state = '';
pagingViewModel.setSearchTerm('');
},
2026-02-14 10:46:58 +00:00
),
Expanded(
2026-03-30 09:40:06 +00:00
child: MerchantListView(
merchants: pagingState.items,
hasMore: pagingState.hasMore,
isLoadingMore: pagingState.isLoadingMore,
onRefresh: pagingViewModel.refresh,
onMerchantTap: (merchant) {
final id = merchant.id;
if (id == null) {
return;
}
_openTerminalSelection(context, id, merchant.name ?? '-');
},
onLoadMore: pagingViewModel.loadMore,
onEndReached: pagingViewModel.loadMore,
2026-02-20 07:43:57 +00:00
),
2026-02-14 10:46:58 +00:00
),
2026-03-30 09:40:06 +00:00
if (pagingState.errorMessage != null &&
pagingState.items.isNotEmpty)
Padding(
padding: EdgeInsets.fromLTRB(
16,
8,
16,
MediaQuery.viewPaddingOf(context).bottom + 8,
),
child: Material(
color: colorScheme.errorContainer,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(
Icons.error_outline,
color: colorScheme.onErrorContainer,
),
const SizedBox(width: 10),
Expanded(
child: Text(
pagingState.errorMessage!,
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: colorScheme.onErrorContainer,
),
),
),
const SizedBox(width: 8),
TextButton(
onPressed: pagingViewModel.loadMore,
child: const Text('Retry'),
),
],
),
),
),
),
2026-02-14 10:46:58 +00:00
],
2026-03-30 09:40:06 +00:00
),
2026-02-13 19:46:02 +00:00
);
}
2026-02-14 10:51:16 +00:00
void _showComingSoon(BuildContext context, String feature) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('$feature is coming soon')));
}
2026-03-30 09:40:06 +00:00
void _showProfile(BuildContext context, LoginUser user) {
2026-02-14 10:51:16 +00:00
showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Profile'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Username: ${user.username}'),
const SizedBox(height: 8),
Text('Role: ${user.role}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
);
},
);
}
2026-02-14 17:57:18 +00:00
void _openTerminalSelection(
BuildContext context,
String merchantId,
String merchantName,
) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => TerminalSelectionScreen(
merchantId: merchantId,
merchantName: merchantName,
),
),
);
}
2026-02-13 19:46:02 +00:00
}