cb_prestige_qr/lib/features/scan/presentation/pages/scan_page.dart
2026-04-27 10:58:43 +06:30

178 lines
6.4 KiB
Dart

import 'dart:async';
import 'package:cb_prestige_qr/core/presentation/widgets/start_loading_overlay.dart';
import 'package:cb_prestige_qr/features/scan/presentation/manager/scan_controller.dart';
import 'package:cb_prestige_qr/features/scan/presentation/pages/scan_result_page.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ScanPage extends ConsumerStatefulWidget {
const ScanPage({super.key});
@override
ConsumerState<ScanPage> createState() => _ScanPageState();
}
class _ScanPageState extends ConsumerState<ScanPage> {
late final MobileScannerController _scannerController;
String? _openedResultValue;
@override
void initState() {
super.initState();
_scannerController = MobileScannerController();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ref.read(scanControllerProvider.notifier).resumeScanning();
unawaited(_scannerController.start());
});
}
@override
void dispose() {
_scannerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ref.listen<bool>(scanControllerProvider.select((s) => s.isScanning), (
previous,
next,
) {
if (previous == next) return;
if (next) {
unawaited(_scannerController.start());
} else {
unawaited(_scannerController.stop());
}
});
ref.listen<String?>(scanControllerProvider.select((s) => s.scannedValue), (
previous,
next,
) {
if (next == null || next == _openedResultValue) return;
_openedResultValue = next;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) return;
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => ScanResultPage(rawValue: next)),
);
_openedResultValue = null;
});
});
final scanState = ref.watch(scanControllerProvider);
void onDetect(BarcodeCapture capture) {
if (!scanState.isScanning) return;
final value = capture.barcodes.isEmpty
? null
: capture.barcodes.first.rawValue;
if (value == null || value.isEmpty) return;
unawaited(
ref.read(scanControllerProvider.notifier).onBarcodeDetected(value),
);
}
return StartLoadingOverlay(
child: Scaffold(
appBar: AppBar(title: const Text('Scan QR')),
body: Stack(
children: [
MobileScanner(controller: _scannerController, onDetect: onDetect),
Align(
alignment: Alignment.center,
child: Container(
width: 250,
height: 250,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
borderRadius: BorderRadius.circular(12),
),
),
),
if (scanState.isProcessing)
const Positioned.fill(
child: ColoredBox(
color: Color(0x66000000),
child: Center(child: CircularProgressIndicator()),
),
),
if (!scanState.isProcessing && scanState.errorMessage != null)
Positioned.fill(
child: ColoredBox(
color: const Color(0x66000000),
child: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Padding(
padding: const EdgeInsets.all(16),
child: Material(
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
scanState.errorMessage == null
? 'Scanned Result'
: 'Scan Error',
style: Theme.of(
context,
).textTheme.titleLarge,
),
const SizedBox(height: 12),
SelectableText(scanState.errorMessage ?? ''),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
ref
.read(
scanControllerProvider
.notifier,
)
.resumeScanning();
},
child: const Text('Scan Again'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back'),
),
),
],
),
],
),
),
),
),
),
),
),
),
),
],
),
),
);
}
}