Files
minhthu/lib/features/scanner/presentation/pages/detail_page.dart
2025-09-16 23:14:35 +07:00

334 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../data/models/scan_item.dart';
import '../providers/form_provider.dart';
import '../providers/scanner_provider.dart';
/// Detail page for editing scan data with 4 text fields and Save/Print buttons
class DetailPage extends ConsumerStatefulWidget {
final String barcode;
const DetailPage({
required this.barcode,
super.key,
});
@override
ConsumerState<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends ConsumerState<DetailPage> {
late final TextEditingController _field1Controller;
late final TextEditingController _field2Controller;
late final TextEditingController _field3Controller;
late final TextEditingController _field4Controller;
@override
void initState() {
super.initState();
_field1Controller = TextEditingController();
_field2Controller = TextEditingController();
_field3Controller = TextEditingController();
_field4Controller = TextEditingController();
// Initialize controllers with existing data if available
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadExistingData();
});
}
@override
void dispose() {
_field1Controller.dispose();
_field2Controller.dispose();
_field3Controller.dispose();
_field4Controller.dispose();
super.dispose();
}
/// Load existing data from history if available
void _loadExistingData() {
final history = ref.read(scanHistoryProvider);
final existingScan = history.firstWhere(
(item) => item.barcode == widget.barcode,
orElse: () => ScanItem(barcode: widget.barcode, timestamp: DateTime.now()),
);
_field1Controller.text = existingScan.field1;
_field2Controller.text = existingScan.field2;
_field3Controller.text = existingScan.field3;
_field4Controller.text = existingScan.field4;
// Update form provider with existing data
final formNotifier = ref.read(formProviderFamily(widget.barcode).notifier);
formNotifier.populateWithScanItem(existingScan);
}
@override
Widget build(BuildContext context) {
final formState = ref.watch(formProviderFamily(widget.barcode));
final formNotifier = ref.read(formProviderFamily(widget.barcode).notifier);
// Listen to form state changes for navigation
ref.listen<FormDetailState>(
formProviderFamily(widget.barcode),
(previous, next) {
if (next.isSaveSuccess && (previous?.isSaveSuccess != true)) {
_showSuccessAndNavigateBack(context);
}
},
);
return Scaffold(
appBar: AppBar(
title: const Text('Edit Details'),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(),
),
),
body: Column(
children: [
// Barcode Header
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Barcode',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
Text(
widget.barcode,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
),
),
],
),
),
// Form Fields
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Field 1
_buildTextField(
controller: _field1Controller,
label: 'Field 1',
onChanged: formNotifier.updateField1,
),
const SizedBox(height: 16),
// Field 2
_buildTextField(
controller: _field2Controller,
label: 'Field 2',
onChanged: formNotifier.updateField2,
),
const SizedBox(height: 16),
// Field 3
_buildTextField(
controller: _field3Controller,
label: 'Field 3',
onChanged: formNotifier.updateField3,
),
const SizedBox(height: 16),
// Field 4
_buildTextField(
controller: _field4Controller,
label: 'Field 4',
onChanged: formNotifier.updateField4,
),
const SizedBox(height: 24),
// Error Message
if (formState.error != null) ...[
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.error,
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.error_outline,
color: Theme.of(context).colorScheme.error,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
formState.error!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onErrorContainer,
),
),
),
],
),
),
const SizedBox(height: 16),
],
],
),
),
),
// Action Buttons
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
),
),
child: SafeArea(
child: Row(
children: [
// Save Button
Expanded(
child: ElevatedButton(
onPressed: formState.isLoading ? null : () => _saveData(formNotifier),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
minimumSize: const Size.fromHeight(48),
),
child: formState.isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text('Save'),
),
),
const SizedBox(width: 16),
// Print Button
Expanded(
child: OutlinedButton(
onPressed: formState.isLoading ? null : () => _printData(formNotifier),
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(48),
),
child: const Text('Print'),
),
),
],
),
),
),
],
),
);
}
/// Build text field widget
Widget _buildTextField({
required TextEditingController controller,
required String label,
required void Function(String) onChanged,
}) {
return TextField(
controller: controller,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
),
textCapitalization: TextCapitalization.sentences,
onChanged: onChanged,
);
}
/// Save form data
Future<void> _saveData(FormNotifier formNotifier) async {
// Clear any previous errors
formNotifier.clearError();
// Attempt to save
await formNotifier.saveData();
}
/// Print form data
Future<void> _printData(FormNotifier formNotifier) async {
try {
await formNotifier.printData();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Print dialog opened'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Print failed: ${e.toString()}'),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
}
}
/// Show success message and navigate back
void _showSuccessAndNavigateBack(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Data saved successfully!'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
// Navigate back after a short delay
Future.delayed(const Duration(milliseconds: 1500), () {
if (mounted) {
context.pop();
}
});
}
}