Files
receipity/lib/shop_detail_screen.dart
T

159 lines
4.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// One shopping trip: receipt photo, supermarket, products and costs.
library;
import 'dart:io';
import 'package:flutter/material.dart';
import 'models/shop.dart';
import 'product_detail_screen.dart';
import 'services/product_repository.dart';
import 'utils/dates.dart';
import 'utils/money.dart';
class ShopDetailScreen extends StatefulWidget {
const ShopDetailScreen({
super.key,
required this.repository,
required this.shopId,
});
final ProductRepository repository;
final int shopId;
@override
State<ShopDetailScreen> createState() => _ShopDetailScreenState();
}
class _ShopDetailScreenState extends State<ShopDetailScreen> {
late Future<({Shop shop, List<ShopItem> items})> _future;
@override
void initState() {
super.initState();
_future = _load();
}
Future<({Shop shop, List<ShopItem> items})> _load() async {
final shop = await widget.repository.getShop(widget.shopId);
if (shop == null) {
throw StateError('Shop not found');
}
final items = await widget.repository.getShopItems(widget.shopId);
return (shop: shop, items: items);
}
Future<void> _openReceipt(File file) async {
await showDialog<void>(
context: context,
builder: (context) {
return Dialog(
insetPadding: const EdgeInsets.all(16),
child: InteractiveViewer(
child: Image.file(file, fit: BoxFit.contain),
),
);
},
);
}
Future<void> _openProduct(int productId) async {
await Navigator.of(context).push<void>(
MaterialPageRoute(
builder: (_) => ProductDetailScreen(
repository: widget.repository,
productId: productId,
),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Shopping trip')),
body: FutureBuilder(
future: _future,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'Could not load this trip.',
style: theme.textTheme.bodyLarge,
),
);
}
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final shop = snapshot.data!.shop;
final items = snapshot.data!.items;
final receipt = shop.receiptImagePath;
final receiptFile =
receipt == null ? null : File(receipt);
final hasReceipt =
receiptFile != null && receiptFile.existsSync();
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
children: [
Text(shop.supermarket, style: theme.textTheme.headlineMedium),
const SizedBox(height: 4),
Text(
formatDate(shop.shoppedAt),
style: theme.textTheme.bodyLarge,
),
if (hasReceipt) ...[
const SizedBox(height: 16),
GestureDetector(
onTap: () => _openReceipt(receiptFile),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.file(
receiptFile,
height: 180,
width: double.infinity,
fit: BoxFit.cover,
cacheWidth: 900,
),
),
),
],
const SizedBox(height: 24),
Text('Products', style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
for (final item in items)
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(item.name),
subtitle: item.quantity == null
? null
: Text(
'${item.quantity} × ${formatEuro(item.unitPrice ?? 0)}',
),
trailing: Text(
formatEuro(item.price),
style: theme.textTheme.titleMedium,
),
onTap: () => _openProduct(item.productId),
),
const Divider(),
ListTile(
contentPadding: EdgeInsets.zero,
title: Text('Total', style: theme.textTheme.titleMedium),
trailing: Text(
formatEuro(shop.total),
style: theme.textTheme.titleLarge,
),
),
],
);
},
),
);
}
}