Temporary setup of alternative to advertisements
This commit is contained in:
parent
ad2d932842
commit
57a9793a40
14 changed files with 653 additions and 56 deletions
|
|
@ -28,6 +28,34 @@ Future<void> captureReceiptForVehicle(BuildContext context, String vehicleId) as
|
|||
|
||||
final keepMaxQuality = context.read<AppState>().keepMaxQualityReceiptPhotos;
|
||||
final picker = ImagePicker();
|
||||
|
||||
// Raised *before* the camera/gallery opens rather than after it returns.
|
||||
// [ImagePicker.pickImage] doesn't complete when the camera closes — it
|
||||
// also downscales the captured photo (several MB straight off the
|
||||
// sensor), which takes seconds on a busy device. With the progress
|
||||
// shown only afterward, that whole stretch left the user staring at an
|
||||
// unchanged Receipts list with no sign anything was happening, which
|
||||
// reads as "the capture did nothing" — and tapping away during it
|
||||
// genuinely does abandon the capture.
|
||||
var progressShowing = false;
|
||||
void showProgress() {
|
||||
if (progressShowing || !context.mounted) return;
|
||||
progressShowing = true;
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const _ReceiptProgressDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
void hideProgress() {
|
||||
if (!progressShowing || !context.mounted) return;
|
||||
progressShowing = false;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
showProgress();
|
||||
|
||||
XFile? photo;
|
||||
try {
|
||||
photo = await picker.pickImage(
|
||||
|
|
@ -37,6 +65,7 @@ Future<void> captureReceiptForVehicle(BuildContext context, String vehicleId) as
|
|||
maxHeight: keepMaxQuality ? null : receiptImageMaxDimension,
|
||||
);
|
||||
} catch (e) {
|
||||
hideProgress();
|
||||
if (context.mounted) {
|
||||
final sourceLabel = source == ImageSource.camera ? 'camera' : 'photo library';
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
@ -45,16 +74,16 @@ Future<void> captureReceiptForVehicle(BuildContext context, String vehicleId) as
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (photo == null) return;
|
||||
// Cancelled (backed out of the camera, or didn't confirm the shot) —
|
||||
// nothing to say about that, it was deliberate.
|
||||
if (photo == null) {
|
||||
hideProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
final imageFile = File(photo.path);
|
||||
|
||||
if (!context.mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
|
||||
final ocrService = OcrService();
|
||||
String recognizedText = '';
|
||||
|
|
@ -69,7 +98,7 @@ Future<void> captureReceiptForVehicle(BuildContext context, String vehicleId) as
|
|||
final parsed = ReceiptParser.parse(recognizedText);
|
||||
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop(); // close the OCR loading spinner
|
||||
hideProgress();
|
||||
|
||||
final state = parsed.state;
|
||||
if (state != null && state != 'MO') {
|
||||
|
|
@ -90,6 +119,45 @@ Future<void> captureReceiptForVehicle(BuildContext context, String vehicleId) as
|
|||
}
|
||||
}
|
||||
|
||||
/// Shown from the moment the camera/gallery is opened until the parsed
|
||||
/// receipt is ready to review. Deliberately says what's happening rather
|
||||
/// than showing a bare spinner: the wait here is mostly downscaling and
|
||||
/// reading the photo, which is long enough on a slow device that an
|
||||
/// unexplained spinner invites tapping away mid-capture.
|
||||
class _ReceiptProgressDialog extends StatelessWidget {
|
||||
const _ReceiptProgressDialog();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
// Backing out here wouldn't cancel the underlying capture, it would
|
||||
// just hide it and strand the user.
|
||||
canPop: false,
|
||||
child: Dialog(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 20),
|
||||
Text('Reading your receipt…', style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'This can take a few seconds.',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Missouri's fuel tax refund only applies to fuel bought in Missouri —
|
||||
/// warn if the receipt's address is somewhere else, and let the user
|
||||
/// decide whether to log it anyway (e.g. it might still be worth tracking
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue