Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

<!-- Storage - older Android -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<!-- Storage - Android 13+ -->
<!-- Full storage access for documents/PDFs (all Android versions) -->
Expand Down
81 changes: 66 additions & 15 deletions lib/ui/process_ui/widgets/document_upload_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import 'dart:io';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
Expand Down Expand Up @@ -560,14 +562,38 @@ class _DocumentUploadControlState extends State<DocumentUploadControl> {
? null
: () async {
_documentScanClickedAudit();
bool isGranted = false;
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt >= 33) {
Comment on lines +567 to +568
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for device info retrieval.

The call to DeviceInfoPlugin().androidInfo can throw an exception (e.g., on unsupported platforms or plugin initialization failures), but there's no try-catch block. Consider adding error handling to gracefully fallback or show a user-friendly message.

🛡️ Proposed fix with error handling
_documentScanClickedAudit();
bool isGranted = false;
try {
  if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    if (androidInfo.version.sdkInt >= 33) {
      final photosStatus = await Permission.photos.request();
      isGranted = photosStatus.isGranted;
    } else {
      final storageStatus = await Permission.storage.request();
      isGranted = storageStatus.isGranted;
    }
  } else {
    final storageStatus = await Permission.storage.request();
    isGranted = storageStatus.isGranted;
  }
} catch (e) {
  debugPrint("Error checking device info or permissions: $e");
  // Fallback to legacy permission on error
  final storageStatus = await Permission.storage.request();
  isGranted = storageStatus.isGranted;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ui/process_ui/widgets/document_upload_control.dart` around lines 567 -
568, Wrap the DeviceInfoPlugin().androidInfo call and the surrounding
platform/permission logic inside a try-catch to handle exceptions from device
info retrieval; in the catch block log the error (e.g., debugPrint or process
logger) and fall back to requesting legacy storage permission so the flow still
proceeds. Update the block around DeviceInfoPlugin/androidInfo (and related
permission requests like Permission.photos.request and
Permission.storage.request) to use the try-catch, ensure
_documentScanClickedAudit() still runs before the try, and set the isGranted
boolean based on the fallback storage permission when an exception occurs.

// Android 13+: Granular media permission
final photosStatus = await Permission.photos.request();
isGranted = photosStatus.isGranted;
} else {
// Android 12 and below: Legacy storage permission
final storageStatus = await Permission.storage.request();
isGranted = storageStatus.isGranted;
}
} else {
final storageStatus = await Permission.storage.request();
isGranted = storageStatus.isGranted;
}
if (!isGranted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Storage permission is required to select documents.'),
backgroundColor: Colors.red,
),
);
return;
}
Comment on lines +565 to +589
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Extract duplicate permission logic into a helper method.

The identical permission-checking logic appears in both mobile (lines 565-589) and responsive (lines 860-895) layout handlers, violating the DRY principle. Extract this into a reusable helper method to improve maintainability.

♻️ Proposed refactor to eliminate duplication

Add this helper method to the _DocumentUploadControlState class:

Future<bool> _requestStoragePermission() async {
  if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    if (androidInfo.version.sdkInt >= 33) {
      // Android 13+: Granular media permission
      final photosStatus = await Permission.photos.request();
      return photosStatus.isGranted;
    } else {
      // Android 12 and below: Legacy storage permission
      final storageStatus = await Permission.storage.request();
      return storageStatus.isGranted;
    }
  } else {
    final storageStatus = await Permission.storage.request();
    return storageStatus.isGranted;
  }
}

Then replace both occurrences with:

_documentScanClickedAudit();
final isGranted = await _requestStoragePermission();
if (!isGranted) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Storage permission is required to select documents.'),
      backgroundColor: Colors.red,
    ),
  );
  return;
}
var doc = await Navigator.push(
  context,
  MaterialPageRoute(
      builder: (context) =>
          CustomScanner(field: widget.field)),
);
await addDocument(doc, widget.field, referenceNumber);
await getScannedDocuments(widget.field);

Also applies to: 860-895

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ui/process_ui/widgets/document_upload_control.dart` around lines 565 -
589, Duplicate permission-request logic in the mobile and responsive handlers
should be extracted into a single helper on the _DocumentUploadControlState; add
a private method (e.g. Future<bool> _requestStoragePermission()) that contains
the Platform.isAndroid/device SDK check and returns whether permission was
granted, then replace both duplicated blocks with a call to
_documentScanClickedAudit(); final isGranted = await
_requestStoragePermission(); and keep the existing SnackBar/return behavior if
false, followed by the existing navigator/addDocument/getScannedDocuments flow
(references: _DocumentUploadControlState, _requestStoragePermission,
_documentScanClickedAudit, addDocument, getScannedDocuments).

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add mounted check before using context after async operations.

The code uses context in ScaffoldMessenger.of(context) (line 582) after multiple await calls without verifying the widget is still mounted. This can cause exceptions if the widget is disposed during the async operations.

🛡️ Proposed fix to add mounted check
                                   if (!isGranted) {
+                                    if (!mounted) return;
                                     ScaffoldMessenger.of(context).showSnackBar(
                                       const SnackBar(
                                         content: Text('Storage permission is required to select documents.'),
                                         backgroundColor: Colors.red,
                                       ),
                                     );
                                     return;
                                   }
+                                  if (!mounted) return;
                                   var doc = await Navigator.push(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ui/process_ui/widgets/document_upload_control.dart` around lines 565 -
589, This code calls ScaffoldMessenger.of(context) after several awaits; add a
mounted check in the surrounding State method (the async handler that contains
this permission/request logic, e.g., the onPressed/_pickDocument method)
immediately after the awaited permission calls and before using context—i.e.,
after the final await(s) insert "if (!mounted) return;" so the method exits if
the widget was disposed, preventing use of context when unmounted; ensure the
check is placed before the ScaffoldMessenger.of(context).showSnackBar(...) call
and any other context usage.

var doc = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CustomScanner(
field: widget.field)),
);

await addDocument(
doc, widget.field, referenceNumber);
await getScannedDocuments(widget.field);
Expand Down Expand Up @@ -828,20 +854,45 @@ class _DocumentUploadControlState extends State<DocumentUploadControl> {
minimumSize: Size(100.w, 48.h),
),
onPressed: (documentController.text == "")
? null
: () async {
_documentScanClickedAudit();
var doc = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomScanner(
field: widget.field)),
);
await addDocument(
doc, widget.field, referenceNumber);

await getScannedDocuments(widget.field);
},
? null
: () async {
_documentScanClickedAudit();
bool isGranted = false;
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt >= 33) {
// Android 13+: Granular media permission
final photosStatus = await Permission.photos.request();
isGranted = photosStatus.isGranted;
} else {
// Android 12 and below: Legacy storage permission
final storageStatus = await Permission.storage.request();
isGranted = storageStatus.isGranted;
}
} else {
final storageStatus = await Permission.storage.request();
isGranted = storageStatus.isGranted;
}
if (!isGranted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Storage permission is required to select documents.'),
backgroundColor: Colors.red,
),
);
return;
}
var doc = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CustomScanner(
field: widget.field)),
);
await addDocument(
doc, widget.field, referenceNumber);
await getScannedDocuments(widget.field);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expand Down