From 648d5225f6abcd4fe1c060e3afeea14ecd38903c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Dec 2025 21:48:16 +0800 Subject: [PATCH] :bug: Ensure mobile site management request permission --- android/app/src/main/AndroidManifest.xml | 4 +- .../sites/file_management_section.dart | 171 +++++++++--------- pubspec.lock | 48 +++++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 139 insertions(+), 89 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3a885ba0..1e9a7b45 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -12,6 +12,8 @@ + + @@ -159,4 +161,4 @@ - + \ No newline at end of file diff --git a/lib/widgets/sites/file_management_section.dart b/lib/widgets/sites/file_management_section.dart index 7cba41c1..ae395a28 100644 --- a/lib/widgets/sites/file_management_section.dart +++ b/lib/widgets/sites/file_management_section.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; @@ -10,6 +11,7 @@ import 'package:island/pods/site_files.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/sites/file_upload_dialog.dart'; import 'package:island/widgets/sites/file_item.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path/path.dart' as p; @@ -53,6 +55,9 @@ class FileManagementSection extends HookConsumerWidget { PopupMenuButton( icon: const Icon(Symbols.upload), onSelected: (String choice) async { + if (!kIsWeb) { + await Permission.storage.request(); + } List files = []; List>? results; if (choice == 'files') { @@ -65,17 +70,17 @@ class FileManagementSection extends HookConsumerWidget { selectedFiles.files.isEmpty) { return; // User canceled } - files = - selectedFiles.files - .map((f) => File(f.path!)) - .toList(); + files = selectedFiles.files + .map((f) => File(f.path!)) + .toList(); } else if (choice == 'folder') { - final dirPath = - await FilePicker.platform.getDirectoryPath(); + final dirPath = await FilePicker.platform + .getDirectoryPath(); if (dirPath == null) return; results = await _getFilesRecursive(dirPath); - files = - results.map((m) => m['file'] as File).toList(); + files = results + .map((m) => m['file'] as File) + .toList(); if (files.isEmpty) { showSnackBar('noFilesFoundInFolder'.tr()); return; @@ -88,51 +93,46 @@ class FileManagementSection extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => FileUploadDialog( - selectedFiles: files, - site: site, - relativePaths: - results - ?.map( - (m) => m['relativePath'] as String, - ) - .toList(), - onUploadComplete: () { - // Refresh file list - ref.invalidate( - siteFilesProvider( - siteId: site.id, - path: currentPath.value, - ), - ); - }, - ), + builder: (context) => FileUploadDialog( + selectedFiles: files, + site: site, + relativePaths: results + ?.map((m) => m['relativePath'] as String) + .toList(), + onUploadComplete: () { + // Refresh file list + ref.invalidate( + siteFilesProvider( + siteId: site.id, + path: currentPath.value, + ), + ); + }, + ), ); }, - itemBuilder: - (BuildContext context) => [ - PopupMenuItem( - value: 'files', - child: Row( - children: [ - Icon(Symbols.file_copy), - Gap(12), - Text('siteFiles'.tr()), - ], - ), - ), - PopupMenuItem( - value: 'folder', - child: Row( - children: [ - Icon(Symbols.folder), - Gap(12), - Text('siteFolder'.tr()), - ], - ), - ), - ], + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + value: 'files', + child: Row( + children: [ + Icon(Symbols.file_copy), + Gap(12), + Text('siteFiles'.tr()), + ], + ), + ), + PopupMenuItem( + value: 'folder', + child: Row( + children: [ + Icon(Symbols.folder), + Gap(12), + Text('siteFolder'.tr()), + ], + ), + ), + ], style: ButtonStyle( visualDensity: const VisualDensity( horizontal: -4, @@ -156,19 +156,17 @@ class FileManagementSection extends HookConsumerWidget { IconButton( icon: Icon(Symbols.arrow_back), onPressed: () { - final pathParts = - currentPath.value! - .split('/') - .where((part) => part.isNotEmpty) - .toList(); + final pathParts = currentPath.value! + .split('/') + .where((part) => part.isNotEmpty) + .toList(); if (pathParts.isEmpty) { currentPath.value = null; } else { pathParts.removeLast(); - currentPath.value = - pathParts.isEmpty - ? null - : pathParts.join('/'); + currentPath.value = pathParts.isEmpty + ? null + : pathParts.join('/'); } }, visualDensity: const VisualDensity( @@ -185,11 +183,10 @@ class FileManagementSection extends HookConsumerWidget { child: Text('siteRoot'.tr()), ), ...() { - final parts = - currentPath.value! - .split('/') - .where((part) => part.isNotEmpty) - .toList(); + final parts = currentPath.value! + .split('/') + .where((part) => part.isNotEmpty) + .toList(); final widgets = []; String currentBuilder = ''; for (final part in parts) { @@ -200,8 +197,8 @@ class FileManagementSection extends HookConsumerWidget { widgets.addAll([ const Text(' / '), InkWell( - onTap: - () => currentPath.value = pathToSet, + onTap: () => + currentPath.value = pathToSet, child: Text(part), ), ]); @@ -253,33 +250,31 @@ class FileManagementSection extends HookConsumerWidget { return FileItem( file: file, site: site, - onNavigateDirectory: - (path) => currentPath.value = path, + onNavigateDirectory: (path) => + currentPath.value = path, ); }, ); }, - loading: - () => const Center(child: CircularProgressIndicator()), - error: - (error, stack) => Center( - child: Column( - children: [ - Text('failedToLoadFiles'.tr()), - const Gap(8), - ElevatedButton( - onPressed: - () => ref.invalidate( - siteFilesProvider( - siteId: site.id, - path: currentPath.value, - ), - ), - child: Text('retry'.tr()), + loading: () => + const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center( + child: Column( + children: [ + Text('failedToLoadFiles'.tr()), + const Gap(8), + ElevatedButton( + onPressed: () => ref.invalidate( + siteFilesProvider( + siteId: site.id, + path: currentPath.value, ), - ], + ), + child: Text('retry'.tr()), ), - ), + ], + ), + ), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 9fe952bb..bba0ee77 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1917,6 +1917,54 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0+3" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ed80ff06..3ecf9e87 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -171,6 +171,7 @@ dependencies: http_parser: ^4.1.2 flutter_code_editor: ^0.3.5 skeletonizer: ^2.1.1 + permission_handler: ^12.0.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index ef809211..814ca910 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); PasteboardPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PasteboardPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi")); RecordWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 912dc878..1d38bdf5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -21,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST media_kit_libs_windows_video media_kit_video pasteboard + permission_handler_windows protocol_handler_windows record_windows screen_retriever_windows