From ff7ec6726312444cf856cf18c9758f8e3503b759 Mon Sep 17 00:00:00 2001 From: Tower stacking <116479750+wuchenxiuwu@users.noreply.github.com> Date: Sun, 22 Mar 2026 18:53:15 +0800 Subject: [PATCH 1/3] Fix the issue of deleting photos randomly This modification ----------------- 1. Added _ cameraFiles list ( List < Map < String, String > > ) to record the photo / video files generated by this session ( including unique ID and absolute path ). 2. The _ generateId() method is added to generate a unique identifier based on timestamp + random number. 3. _ snap and _ clip logical reconstruction 4. Generate fileId and save { id, path } to _ cameraFiles immediately after obtaining the file. 5. Before deleting a file, first search in the list through fileId and path, confirm that the file belongs to this session, and then delete it to avoid deleting the file in the user's photo album by mistake. 6. The deletion operation is uniformly placed at the end of the try block, and a delete Error is added to ignore the deletion failure ( the file may have been removed, etc. ). 7. In the catch block, if fileId is not empty, the corresponding record will be removed from _ cameraFiles to prevent residue. 8. New resource cleanup method 9. dispose() is empty in the original version, traverse _ cameraFiles in the modified version and try to delete all remaining temporary files, and finally empty the list. Ensure that temporary files are not left behind to occupy storage space when an application exits or capability is uninstalled. --- .../capabilities/camera_capability.dart | 73 ++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/flutter_app/lib/services/capabilities/camera_capability.dart b/flutter_app/lib/services/capabilities/camera_capability.dart index 8a1dd10..d881810 100644 --- a/flutter_app/lib/services/capabilities/camera_capability.dart +++ b/flutter_app/lib/services/capabilities/camera_capability.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:ui' as ui; import 'package:camera/camera.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -8,6 +9,12 @@ import 'capability_handler.dart'; class CameraCapability extends CapabilityHandler { List? _cameras; + + // Record the camera file generated during this session (unique ID+path) + // Ensure that only files created by oneself are deleted during deletion to avoid accidentally deleting user albums + /**Fixed since March 22, 2026 + submitter:wuchenxiuwu */ + final List> _cameraFiles = []; @override String get name => 'camera'; @@ -83,28 +90,45 @@ class CameraCapability extends CapabilityHandler { } } + // Generate a unique ID (timestamp+random number) + String _generateId() { + final now = DateTime.now().millisecondsSinceEpoch; + final random = Random().nextInt(1000000); + return '${now}_$random'; + } + Future _snap(Map params) async { CameraController? controller; + String? fileId; try { final facing = params['facing'] as String?; controller = await _createController(facing: facing); - // Brief settle time for auto-exposure/focus await Future.delayed(const Duration(milliseconds: 500)); final file = await controller.takePicture(); - final bytes = await File(file.path).readAsBytes(); + final path = file.path; + + // Record the files generated this time in the inventory + fileId = _generateId(); + _cameraFiles.add({'id': fileId, 'path': path}); + + final bytes = await File(path).readAsBytes(); final b64 = base64Encode(bytes); - // Get image dimensions final codec = await ui.instantiateImageCodec(bytes); final frame = await codec.getNextFrame(); final width = frame.image.width; final height = frame.image.height; frame.image.dispose(); - // Clean up temp file - await File(file.path).delete().catchError((_) => File(file.path)); + // Only delete files when they are on the list to prevent accidental deletion of the album + final index = _cameraFiles.indexWhere((item) => item['id'] == fileId && item['path'] == path); + if (index != -1) { + _cameraFiles.removeAt(index); + await File(path).delete().catchError((_) => File(path)); + } + return NodeFrame.response('', payload: { 'base64': b64, 'format': 'jpg', @@ -112,18 +136,22 @@ class CameraCapability extends CapabilityHandler { 'height': height, }); } catch (e) { + //Clean up records in the inventory when errors occur to avoid residue + if (fileId != null) { + _cameraFiles.removeWhere((item) => item['id'] == fileId); + } return NodeFrame.response('', error: { 'code': 'CAMERA_ERROR', 'message': '$e', }); } finally { - // Always release the camera await controller?.dispose(); } } Future _clip(Map params) async { CameraController? controller; + String? fileId; try { final durationMs = params['durationMs'] as int? ?? 5000; final facing = params['facing'] as String?; @@ -131,9 +159,22 @@ class CameraCapability extends CapabilityHandler { await controller.startVideoRecording(); await Future.delayed(Duration(milliseconds: durationMs)); final file = await controller.stopVideoRecording(); - final bytes = await File(file.path).readAsBytes(); + final path = file.path; + + //Record the files generated this time in the inventory + fileId = _generateId(); + _cameraFiles.add({'id': fileId, 'path': path}); + + final bytes = await File(path).readAsBytes(); final b64 = base64Encode(bytes); - await File(file.path).delete().catchError((_) => File(file.path)); + + //Only delete files when they are on the list to prevent accidental deletion of user albums + final index = _cameraFiles.indexWhere((item) => item['id'] == fileId && item['path'] == path); + if (index != -1) { + _cameraFiles.removeAt(index); + await File(path).delete().catchError((_) => File(path)); + } + return NodeFrame.response('', payload: { 'base64': b64, 'format': 'mp4', @@ -141,17 +182,29 @@ class CameraCapability extends CapabilityHandler { 'hasAudio': false, }); } catch (e) { + //Clean up records in the inventory when errors occur to avoid residue + if (fileId != null) { + _cameraFiles.removeWhere((item) => item['id'] == fileId); + } return NodeFrame.response('', error: { 'code': 'CAMERA_ERROR', 'message': '$e', }); } finally { - // Always release the camera await controller?.dispose(); } } + //Clean up all undeleted temporary files when the application exits void dispose() { - // No persistent controller to clean up anymore + for (final item in _cameraFiles) { + final path = item['path']; + if (path != null) { + try { + File(path).deleteSync(); + } catch (_) {} + } + } + _cameraFiles.clear(); } } From 6e43ae45870c6248bd0770eb5c9be1dac9ebb240 Mon Sep 17 00:00:00 2001 From: Tower stacking <116479750+wuchenxiuwu@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:03:10 +0800 Subject: [PATCH 2/3] Synchronize modifications, details can be found in the submission This modification is as follows ------------------ 1. Added file tracking and unique identifier mechanism ------------------- 1.1 Added _screenFiles list: List> to record screen recording files generated during the current session (including unique ID and absolute path). Added _generateId() method: generates a unique identifier based on timestamp + random number to distinguish different files. -------------------- 2. Modified _record method logic --------------------- 2.1 File tracking: after obtaining the file path, immediately generate a fileId and store {id, path} into _screenFiles. 2.2 Pre-deletion validation: after reading the file content, look up the corresponding record in the list using fileId and path to confirm the file belongs to the current session before deleting, preventing accidental deletion of user album files. 2.3 Post-deletion cleanup: after successful deletion, remove the record from the list. 2.4 Exception cleanup: in the catch block, if fileId is not null, remove the corresponding record from _screenFiles to prevent leftover entries. 2.5 Removed direct deletion from the original version: the original version directly called file.delete() after reading the file without any ownership validation; the modified version now deletes only after validation through the list. 2.6 Permission methods remain unchanged: checkPermission and requestPermission still return true directly, as screen recording permissions are handled via the system consent dialog each time it is invoked, so no changes are required. --- .../capabilities/screen_capability.dart | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/flutter_app/lib/services/capabilities/screen_capability.dart b/flutter_app/lib/services/capabilities/screen_capability.dart index 4645fa3..ebf36d8 100644 --- a/flutter_app/lib/services/capabilities/screen_capability.dart +++ b/flutter_app/lib/services/capabilities/screen_capability.dart @@ -1,10 +1,17 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import '../../models/node_frame.dart'; import '../native_bridge.dart'; import 'capability_handler.dart'; class ScreenCapability extends CapabilityHandler { + //File List: Record the screen recording files generated for this session (unique ID+path) + // Used to ensure that only files created by oneself are deleted during deletion, avoiding accidental deletion of user albums + /**Fixed since March 22, 2026 + submitter:wuchenxiuwu */ + final List> _screenFiles = []; + @override String get name => 'screen'; @@ -12,33 +19,31 @@ class ScreenCapability extends CapabilityHandler { List get commands => ['record']; @override - Future checkPermission() async { - // Screen recording always requires user consent each time (Play Store requirement). - // Permission is requested per-invocation via the MediaProjection consent dialog. - return true; - } + Future checkPermission() async => true; @override Future requestPermission() async => true; @override Future handle(String command, Map params) async { - switch (command) { - case 'screen.record': - return _record(params); - default: - return NodeFrame.response('', error: { - 'code': 'UNKNOWN_COMMAND', - 'message': 'Unknown screen command: $command', - }); - } + if (command == 'screen.record') return _record(params); + return NodeFrame.response('', error: { + 'code': 'UNKNOWN_COMMAND', + 'message': 'Unknown screen command: $command', + }); + } + + // Generate a unique ID (timestamp+random number) to distinguish between different files + String _generateId() { + final now = DateTime.now().millisecondsSinceEpoch; + final random = Random().nextInt(1000000); + return '${now}_$random'; } Future _record(Map params) async { + String? fileId; try { final durationMs = params['durationMs'] as int? ?? 5000; - - // This triggers the mandatory user consent dialog every time final filePath = await NativeBridge.requestScreenCapture(durationMs); if (filePath == null || filePath.isEmpty) { @@ -48,6 +53,10 @@ class ScreenCapability extends CapabilityHandler { }); } + // Record the files generated this time in the inventory + fileId = _generateId(); + _screenFiles.add({'id': fileId, 'path': filePath}); + final file = File(filePath); if (!await file.exists()) { return NodeFrame.response('', error: { @@ -58,13 +67,23 @@ class ScreenCapability extends CapabilityHandler { final bytes = await file.readAsBytes(); final b64 = base64Encode(bytes); - await file.delete().catchError((_) => file); + + // Only delete files when they are in the list to prevent accidental deletion of user albums + final index = _screenFiles.indexWhere((item) => item['id'] == fileId && item['path'] == filePath); + if (index != -1) { + _screenFiles.removeAt(index); + await file.delete().catchError((_) => file); + } return NodeFrame.response('', payload: { 'base64': b64, 'format': 'mp4', }); } catch (e) { + // Clean up records in the inventory when errors occur to avoid residue + if (fileId != null) { + _screenFiles.removeWhere((item) => item['id'] == fileId); + } return NodeFrame.response('', error: { 'code': 'SCREEN_ERROR', 'message': '$e', From 5c495e8fbfc3e535b749b3fe86c99accd162b8e4 Mon Sep 17 00:00:00 2001 From: Tower stacking <116479750+wuchenxiuwu@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:32:20 +0800 Subject: [PATCH 3/3] Update flutter_app/lib/services/capabilities/camera_capability.dart Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .../lib/services/capabilities/camera_capability.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flutter_app/lib/services/capabilities/camera_capability.dart b/flutter_app/lib/services/capabilities/camera_capability.dart index d881810..6d88e27 100644 --- a/flutter_app/lib/services/capabilities/camera_capability.dart +++ b/flutter_app/lib/services/capabilities/camera_capability.dart @@ -125,8 +125,12 @@ class CameraCapability extends CapabilityHandler { // Only delete files when they are on the list to prevent accidental deletion of the album final index = _cameraFiles.indexWhere((item) => item['id'] == fileId && item['path'] == path); if (index != -1) { - _cameraFiles.removeAt(index); - await File(path).delete().catchError((_) => File(path)); + try { + await File(path).delete(); + _cameraFiles.removeAt(index); + } catch (_) { + // Keep tracking entry so dispose() can retry cleanup later. + } } return NodeFrame.response('', payload: {