fixes before build
This commit is contained in:
+172
-2
@@ -1,10 +1,14 @@
|
||||
/// Dashboard of catalog, trips, lists, and this month's spend.
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import 'models/app_overview.dart';
|
||||
import 'models/shop.dart';
|
||||
import 'models/supermarket.dart';
|
||||
import 'services/app_settings.dart';
|
||||
import 'services/product_repository.dart';
|
||||
import 'shop_detail_screen.dart';
|
||||
@@ -36,8 +40,10 @@ class HomeScreen extends StatefulWidget {
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
AppOverview? _overview;
|
||||
List<Supermarket> _stores = const [];
|
||||
Object? _error;
|
||||
int _loadToken = 0;
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -56,9 +62,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
final token = ++_loadToken;
|
||||
try {
|
||||
final overview = await widget.repository.getOverview();
|
||||
final stores = await widget.repository.getSupermarkets();
|
||||
if (!mounted || token != _loadToken) return;
|
||||
setState(() {
|
||||
_overview = overview;
|
||||
_stores = stores;
|
||||
_error = null;
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -77,6 +85,60 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
if (mounted) _load();
|
||||
}
|
||||
|
||||
Future<void> _openBonusCard(Supermarket store) async {
|
||||
final path = store.bonusImagePath;
|
||||
if (path != null && path.isNotEmpty && File(path).existsSync()) {
|
||||
await _viewBonusCard(path);
|
||||
return;
|
||||
}
|
||||
await _pickBonusCard(store);
|
||||
}
|
||||
|
||||
Future<void> _viewBonusCard(String path) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
child: InteractiveViewer(
|
||||
child: Image.file(File(path), fit: BoxFit.contain),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickBonusCard(Supermarket store) async {
|
||||
final source = await showModalBottomSheet<ImageSource>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_camera_outlined),
|
||||
title: const Text('Take photo'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.camera),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library_outlined),
|
||||
title: const Text('Pick from gallery'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.gallery),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (source == null || store.id == null) return;
|
||||
final file = await _picker.pickImage(source: source, imageQuality: 85);
|
||||
if (file == null) return;
|
||||
await widget.repository.setSupermarketBonusCard(store.id!, file.path);
|
||||
if (mounted) _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
@@ -97,11 +159,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _HomeBody(
|
||||
overview: _overview!,
|
||||
stores: _stores,
|
||||
onOpenProducts: widget.onOpenProducts,
|
||||
onOpenLists: widget.onOpenLists,
|
||||
onOpenShopLog: widget.onOpenShopLog,
|
||||
onOpenAdd: widget.onOpenAdd,
|
||||
onOpenShop: _openShop,
|
||||
onOpenBonusCard: _openBonusCard,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -110,24 +174,29 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
class _HomeBody extends StatelessWidget {
|
||||
const _HomeBody({
|
||||
required this.overview,
|
||||
required this.stores,
|
||||
required this.onOpenProducts,
|
||||
required this.onOpenLists,
|
||||
required this.onOpenShopLog,
|
||||
required this.onOpenAdd,
|
||||
required this.onOpenShop,
|
||||
required this.onOpenBonusCard,
|
||||
});
|
||||
|
||||
final AppOverview overview;
|
||||
final List<Supermarket> stores;
|
||||
final VoidCallback onOpenProducts;
|
||||
final VoidCallback onOpenLists;
|
||||
final VoidCallback onOpenShopLog;
|
||||
final VoidCallback onOpenAdd;
|
||||
final ValueChanged<Shop> onOpenShop;
|
||||
final ValueChanged<Supermarket> onOpenBonusCard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final stores = SettingsScope.maybeOf(context)?.supermarkets ?? const [];
|
||||
final settingsStores =
|
||||
SettingsScope.maybeOf(context)?.supermarkets ?? stores;
|
||||
final empty =
|
||||
overview.productCount == 0 &&
|
||||
overview.tripCount == 0 &&
|
||||
@@ -205,6 +274,27 @@ class _HomeBody extends StatelessWidget {
|
||||
onTap: onOpenLists,
|
||||
),
|
||||
],
|
||||
if (stores.isNotEmpty) ...[
|
||||
const SizedBox(height: 24),
|
||||
Text('Bonus cards', style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 148,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stores.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final store = stores[index];
|
||||
return _BonusCardTile(
|
||||
store: store,
|
||||
stores: settingsStores,
|
||||
onTap: () => onOpenBonusCard(store),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
if (overview.recentShops.isNotEmpty) ...[
|
||||
const SizedBox(height: 24),
|
||||
Text('Recent trips', style: theme.textTheme.titleMedium),
|
||||
@@ -215,7 +305,7 @@ class _HomeBody extends StatelessWidget {
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
title: shop.name == null || shop.name!.isEmpty
|
||||
? StoreTag(name: shop.supermarket, stores: stores)
|
||||
? StoreTag(name: shop.supermarket, stores: settingsStores)
|
||||
: Text(shop.name!),
|
||||
subtitle: Text(
|
||||
[
|
||||
@@ -233,6 +323,86 @@ class _HomeBody extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _BonusCardTile extends StatelessWidget {
|
||||
const _BonusCardTile({
|
||||
required this.store,
|
||||
required this.stores,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final Supermarket store;
|
||||
final List<Supermarket> stores;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scheme = theme.colorScheme;
|
||||
final path = store.bonusImagePath;
|
||||
final hasCard =
|
||||
path != null && path.isNotEmpty && File(path).existsSync();
|
||||
|
||||
return SizedBox(
|
||||
width: 220,
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: !hasCard
|
||||
? ColoredBox(
|
||||
color: store.color.withValues(alpha: 0.18),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.card_membership_outlined,
|
||||
color: store.color,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Add bonus card',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Image.file(
|
||||
File(path),
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
StoreTag(name: store.name, stores: stores, compact: true),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
hasCard
|
||||
? Icons.zoom_in
|
||||
: Icons.camera_alt_outlined,
|
||||
size: 16,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatCard extends StatelessWidget {
|
||||
const _StatCard({
|
||||
required this.icon,
|
||||
|
||||
Reference in New Issue
Block a user