feat: add scan page with record list and send logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-05-07 19:57:57 +08:00
parent c3b8ce02a2
commit 54103a4f96

127
lib/pages/scan_page.dart Normal file
View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pad_scanner/models/scan_record.dart';
import 'package:pad_scanner/services/scanner_service.dart';
import 'package:pad_scanner/services/api_service.dart';
import 'package:pad_scanner/pages/settings_page.dart';
class ScanPage extends StatefulWidget {
const ScanPage({super.key});
@override
State<ScanPage> createState() => _ScanPageState();
}
class _ScanPageState extends State<ScanPage> {
final _scannerService = ScannerService();
final _apiService = ApiService();
final _records = <ScanRecord>[];
String _status = 'Waiting for scan...';
@override
void initState() {
super.initState();
_startListening();
}
void _startListening() {
_scannerService.scanResults.listen((result) async {
final record = ScanRecord(
barcode: result.barcode,
codeType: result.codeType,
timestamp: DateTime.now(),
);
setState(() {
_records.insert(0, record);
_status = 'Sending...';
});
final prefs = await SharedPreferences.getInstance();
final url = prefs.getString('api_url') ?? '';
if (url.isEmpty) {
setState(() {
record.status = SendStatus.failed;
_status = 'No URL configured. Go to Settings.';
});
return;
}
final ok = await _apiService.sendScanData(
url,
barcode: record.barcode,
codeType: record.codeType,
);
if (mounted) {
setState(() {
record.status = ok ? SendStatus.success : SendStatus.failed;
_status = ok ? 'Sent successfully' : 'Send failed';
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('PAD Scanner'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SettingsPage()),
),
),
],
),
body: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: Text(
_status,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
Expanded(
child: _records.isEmpty
? const Center(
child: Text(
'No scans yet.\nPress the scan button on the device.',
textAlign: TextAlign.center))
: ListView.builder(
itemCount: _records.length,
itemBuilder: (context, index) {
final r = _records[index];
return ListTile(
title: Text(r.barcode),
subtitle: Text(
'${r.codeType} ${r.timestamp.toLocal().toIso8601String().substring(0, 19)}'),
trailing: Icon(
r.status == SendStatus.success
? Icons.check_circle
: r.status == SendStatus.failed
? Icons.error
: Icons.hourglass_empty,
color: r.status == SendStatus.success
? Colors.green
: r.status == SendStatus.failed
? Colors.red
: Colors.orange,
),
);
},
),
),
],
),
);
}
}