feat: add HelpText model, service, and controller for dynamic help text management
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 2m27s
Test Suite / flutter-quality (push) Successful in 1m47s

This commit is contained in:
Nils-Johan Gynther
2026-05-13 16:20:04 +02:00
parent 0da4bbf4cf
commit 3d9b124766
11 changed files with 349 additions and 3 deletions
@@ -14,6 +14,7 @@ import '../../pantry/data/pantry_providers.dart';
import '../../pantry/domain/pantry_item.dart';
import '../data/import_providers.dart';
import '../data/receipt_import_session.dart';
import '../domain/help_text_content.dart';
import '../domain/parsed_receipt_item.dart';
import '../../../core/ui/product_picker_field.dart' show ProductOption;
import '../utils/receipt_import_utils.dart';
@@ -37,6 +38,7 @@ class ReceiptImportTab extends ConsumerStatefulWidget {
class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
bool _isLoading = false;
bool _isSaving = false;
bool _isHelpLoading = false;
PlatformFile? _pickedFile;
bool _categoryLoadFailed = false;
bool _globalProductsLoadFailed = false;
@@ -460,6 +462,64 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
);
}
Future<void> _showHelpForReceiptImport() async {
if (_isHelpLoading) return;
setState(() => _isHelpLoading = true);
try {
final token = await ref.read(authStateProvider.future);
final repo = ref.read(importRepositoryProvider);
final help = await repo.fetchHelpTextByKey('receipt_import', token: token);
if (!mounted) return;
await _showHelpDialog(help);
} catch (e) {
if (!mounted) return;
showGlobalErrorDialog(context, 'Kunde inte läsa hjälptexten just nu: $e');
} finally {
if (mounted) setState(() => _isHelpLoading = false);
}
}
Future<void> _showHelpDialog(HelpTextContent help) {
final updatedAt = help.updatedAt;
final updatedAtText = updatedAt == null
? null
: '${updatedAt.year.toString().padLeft(4, '0')}-${updatedAt.month.toString().padLeft(2, '0')}-${updatedAt.day.toString().padLeft(2, '0')}';
return showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(help.title.isEmpty ? 'Hjälp: Kvittoimport' : help.title),
content: SizedBox(
width: 560,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SelectableText(help.content),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
Chip(label: Text('Scope: ${help.scope}')),
if (updatedAtText != null) Chip(label: Text('Uppdaterad: $updatedAtText')),
],
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Stäng'),
),
],
),
);
}
void _deleteItem(int index) {
final items = _items;
if (items == null || index < 0 || index >= items.length) return;
@@ -819,9 +879,28 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ladda upp ett kvitto (PDF eller bild) — raderna tolkas och kan läggas till i ditt inventarie.',
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceVariant),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
'Ladda upp ett kvitto (PDF eller bild) — raderna tolkas och kan läggas till i ditt inventarie.',
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceVariant),
),
),
const SizedBox(width: 8),
TextButton.icon(
onPressed: _isHelpLoading ? null : _showHelpForReceiptImport,
icon: _isHelpLoading
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.help_outline),
label: const Text('Läs hjälp'),
),
],
),
const SizedBox(height: 20),
OutlinedButton.icon(