From 718f29be3568642a1e3469dcf33c9ac149cc76ae Mon Sep 17 00:00:00 2001 From: Donatas Puidokas <104410044+donpui@users.noreply.github.com> Date: Sat, 28 Jan 2023 14:04:16 +0200 Subject: [PATCH 1/4] Multiuser support added --- .../leastauthority/destiny/MainActivity.kt | 25 ++++++++++++ lib/constants/app_constants.dart | 4 +- lib/views/shared/file_picker.dart | 14 ++++++- lib/views/shared/receive.dart | 38 ++++++++----------- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt b/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt index 5dc1f555..15c121bf 100644 --- a/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt +++ b/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper +import android.os.UserManager import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat @@ -19,6 +20,7 @@ class MainActivity : FlutterActivity() { private val FILE_SELECTOR_CHANNEL_NAME = "destiny.android/file_selector" private val SHARE_FILE_CHANNEL_NAME = "destiny.androids/share_file" private val SAVE_AS_CHANNEL_NAME = "destiny.android/save_as" + private val USER_ID_CHANNEL_NAME = "destiny.android/user_id" private var SHARE_FILE_CHANNEL: MethodChannel? = null @@ -44,6 +46,15 @@ class MainActivity : FlutterActivity() { } } + // Get emphmeral user id to be used in accessing a directory name + private fun getUserID(call: MethodCall, result: MethodChannel.Result) { + val userManager = getContext().getSystemService(UserManager::class.java) + + val ephemeralUserId = userManager!!.getSerialNumberForUser(android.os.Process.myUserHandle()) + + result.success(ephemeralUserId); + } + private fun sendFileFromIntent(intent: Intent) { val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri if (SHARE_FILE_CHANNEL != null) { @@ -101,6 +112,20 @@ class MainActivity : FlutterActivity() { } } + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + USER_ID_CHANNEL_NAME + ).setMethodCallHandler { call, result -> + when (call.method) { + "getUserID" -> getUserID(call, result) + else -> result.error( + UNKNOWN_METHOD, + "Unknown method called on save as channel: ${call.method}", + "" + ) + } + } + SHARE_FILE_CHANNEL = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SHARE_FILE_CHANNEL_NAME) diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index 6923b4d0..6e9e1dff 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -179,7 +179,9 @@ const String SETTINGS_SCREEN_SELECT_A_FOLDER_BUTTON = 'SETTINGS_SCREEN_SELECT_A_FOLDER_BUTTON'; const String SETTINGS_SCREEN_BOTTOM_SPACE_PLACEHOLDER = 'SETTINGS_SCREEN_BOTTOM_SPACE_PLACEHOLDER'; -const String ANDROID_DOWNLOADS_FOLDER_PATH = '/storage/emulated/0/Download'; +const String ANDROID_DOWNLOADS_PATH = '/storage/emulated/'; +const String ANDROID_DOWNLOADS_FOLDER = '/Download'; +const int DEFAULT_ANDROID_USER = 0; const String WINDOW_TITLE = "Destiny"; diff --git a/lib/views/shared/file_picker.dart b/lib/views/shared/file_picker.dart index b627eca4..86807973 100644 --- a/lib/views/shared/file_picker.dart +++ b/lib/views/shared/file_picker.dart @@ -165,7 +165,19 @@ Future getDownloadPath() async { if (Platform.isIOS) { directory = await getApplicationDocumentsDirectory(); } else if (Platform.isAndroid) { - directory = Directory(ANDROID_DOWNLOADS_FOLDER_PATH); + const userData = const MethodChannel('destiny.android/user_id'); + int userId = DEFAULT_ANDROID_USER; + + // try determinate emphemeral user id, to select download folder for multiple users + try { + userId = await userData.invokeMethod("getUserID"); + print("USER ID:" + userId.toString()); + } on PlatformException catch (e) { + print(e); + } + directory = Directory(ANDROID_DOWNLOADS_PATH + + userId.toString() + + ANDROID_DOWNLOADS_FOLDER); if (!await directory.exists()) { directory = await getExternalStorageDirectory(); } diff --git a/lib/views/shared/receive.dart b/lib/views/shared/receive.dart index cb393db2..3fc7983c 100644 --- a/lib/views/shared/receive.dart +++ b/lib/views/shared/receive.dart @@ -102,21 +102,11 @@ class ReceiveSharedState extends ChangeNotifier { this.prefs = prefs; }); - if (dartIO.Platform.isAndroid) { - defaultPathForPlatform = ANDROID_DOWNLOADS_FOLDER_PATH; - } else if (dartIO.Platform.isIOS) { - getApplicationDocumentsDirectory().then((downloadsDir) { - setState(() { - defaultPathForPlatform = downloadsDir.path; - }); - }); - } else { - getDownloadsDirectory().then((downloadsDir) { - setState(() { - defaultPathForPlatform = downloadsDir?.path; - }); + getDownloadPath().then((downloadPath) { + setState(() { + defaultPathForPlatform = downloadPath; }); - } + }); } late ProgressSharedState progress = ProgressSharedState(setState, () { @@ -247,14 +237,18 @@ class ReceiveSharedState extends ChangeNotifier { Client createClient() => Client(appSettings.config()); Future _selectSaveDestinationAndroid(String initialFileName) async { - final uri = await MethodChannel("destiny.android/save_as") - .invokeMethod( - "save_as", {"filename": initialFileName}); - - if (uri != null) { - return uri.androidUriToWriteOnlyFile(); - } else { - return Future.error("uri from save as was null"); + try { + final uri = await MethodChannel("destiny.android/save_as") + .invokeMethod( + "save_as", {"filename": initialFileName}); + + if (uri != null) { + return uri.androidUriToWriteOnlyFile(); + } else { + return Future.error("uri from save as was null"); + } + } catch (e) { + return Future.error(e); } } From a670cb18160a3db8f0ad0b7eb47d65f91651269e Mon Sep 17 00:00:00 2001 From: Donatas Puidokas <104410044+donpui@users.noreply.github.com> Date: Sun, 29 Jan 2023 22:23:17 +0200 Subject: [PATCH 2/4] Resolve app crash --- .../leastauthority/destiny/MainActivity.kt | 1 - lib/main.dart | 5 ++++- lib/views/shared/file_picker.dart | 19 +++++++++--------- lib/views/shared/receive.dart | 20 ++++++++----------- lib/views/shared/send.dart | 4 ++-- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt b/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt index 15c121bf..c7ffb9e2 100644 --- a/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt +++ b/android/app/src/main/kotlin/com/leastauthority/destiny/MainActivity.kt @@ -51,7 +51,6 @@ class MainActivity : FlutterActivity() { val userManager = getContext().getSystemService(UserManager::class.java) val ephemeralUserId = userManager!!.getSerialNumberForUser(android.os.Process.myUserHandle()) - result.success(ephemeralUserId); } diff --git a/lib/main.dart b/lib/main.dart index 1aca6d3b..4d512959 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,18 +19,21 @@ import 'locator.dart'; // This is our global ServiceLocator GetIt getIt = GetIt.instance; -void startApp(Config c) { +void startApp(Config c) async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); register(getIt, c); + // Wait for all preferences to be loaded. Getting UserID leads to a crash if + await getIt.allReady(); if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { setWindowMinSize(const Size(900, 800)); setWindowMaxSize(const Size(1600, 1200)); setWindowFrame(Rect.fromLTWH(0, 0, 900, 800)); } + runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (context) => SendSharedState()), ChangeNotifierProvider(create: (context) => ReceiveSharedState()) diff --git a/lib/views/shared/file_picker.dart b/lib/views/shared/file_picker.dart index 86807973..689b56ff 100644 --- a/lib/views/shared/file_picker.dart +++ b/lib/views/shared/file_picker.dart @@ -161,23 +161,23 @@ FilePicker getMediaPicker() { Future getDownloadPath() async { Directory? directory; + try { if (Platform.isIOS) { directory = await getApplicationDocumentsDirectory(); } else if (Platform.isAndroid) { - const userData = const MethodChannel('destiny.android/user_id'); + final userData = MethodChannel('destiny.android/user_id'); int userId = DEFAULT_ANDROID_USER; // try determinate emphemeral user id, to select download folder for multiple users - try { - userId = await userData.invokeMethod("getUserID"); - print("USER ID:" + userId.toString()); - } on PlatformException catch (e) { - print(e); - } + userId = await userData.invokeMethod("getUserID"); + directory = Directory(ANDROID_DOWNLOADS_PATH + userId.toString() + ANDROID_DOWNLOADS_FOLDER); + + print("Download path:" + directory.path); + if (!await directory.exists()) { directory = await getExternalStorageDirectory(); } @@ -185,9 +185,10 @@ Future getDownloadPath() async { directory = await getDownloadsDirectory(); } } catch (err) { - print("Cannot get download folder path"); + print("Cannot get download folder path" + err.toString()); } - return directory!.path; + print("DIRECTORY:" + directory!.path); + return directory.path; } Future getPathConfig() async { diff --git a/lib/views/shared/receive.dart b/lib/views/shared/receive.dart index 3fc7983c..74e16bd2 100644 --- a/lib/views/shared/receive.dart +++ b/lib/views/shared/receive.dart @@ -237,18 +237,14 @@ class ReceiveSharedState extends ChangeNotifier { Client createClient() => Client(appSettings.config()); Future _selectSaveDestinationAndroid(String initialFileName) async { - try { - final uri = await MethodChannel("destiny.android/save_as") - .invokeMethod( - "save_as", {"filename": initialFileName}); - - if (uri != null) { - return uri.androidUriToWriteOnlyFile(); - } else { - return Future.error("uri from save as was null"); - } - } catch (e) { - return Future.error(e); + final uri = await MethodChannel("destiny.android/save_as") + .invokeMethod( + "save_as", {"filename": initialFileName}); + + if (uri != null) { + return uri.androidUriToWriteOnlyFile(); + } else { + return Future.error("uri from save as was null"); } } diff --git a/lib/views/shared/send.dart b/lib/views/shared/send.dart index ad724570..6d6eb42d 100644 --- a/lib/views/shared/send.dart +++ b/lib/views/shared/send.dart @@ -33,8 +33,6 @@ class SendSharedState extends ChangeNotifier { SendScreenStates currentState = SendScreenStates.Initial; - final appSettings = getIt(); - CancelFunc cancelFunc = () { print("No cancel function assigned. Doing nothing"); }; @@ -179,6 +177,8 @@ class SendSharedState extends ChangeNotifier { }, onError: defaultErrorHandler); } + final appSettings = getIt(); + Client createClient() => Client(appSettings.config()); Widget widgetByState( From 9d7ac8a194e47f08a56e68effa0f57f8aad177e3 Mon Sep 17 00:00:00 2001 From: Donatas Puidokas <104410044+donpui@users.noreply.github.com> Date: Mon, 30 Jan 2023 08:22:53 +0200 Subject: [PATCH 3/4] Remove print --- lib/views/shared/file_picker.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/views/shared/file_picker.dart b/lib/views/shared/file_picker.dart index 689b56ff..d2f34b22 100644 --- a/lib/views/shared/file_picker.dart +++ b/lib/views/shared/file_picker.dart @@ -176,8 +176,6 @@ Future getDownloadPath() async { userId.toString() + ANDROID_DOWNLOADS_FOLDER); - print("Download path:" + directory.path); - if (!await directory.exists()) { directory = await getExternalStorageDirectory(); } @@ -187,8 +185,7 @@ Future getDownloadPath() async { } catch (err) { print("Cannot get download folder path" + err.toString()); } - print("DIRECTORY:" + directory!.path); - return directory.path; + return directory!.path; } Future getPathConfig() async { From cf355c44a5f8525331160afb90fed2137a54861f Mon Sep 17 00:00:00 2001 From: Donatas Puidokas <104410044+donpui@users.noreply.github.com> Date: Mon, 30 Jan 2023 08:31:04 +0200 Subject: [PATCH 4/4] Fix lint issue --- lib/views/shared/receive.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/shared/receive.dart b/lib/views/shared/receive.dart index 74e16bd2..7415162a 100644 --- a/lib/views/shared/receive.dart +++ b/lib/views/shared/receive.dart @@ -11,7 +11,6 @@ import 'package:dart_wormhole_william/client/native_client.dart'; import 'package:flutter/material.dart'; import 'package:file_picker/file_picker.dart' as f; import 'package:flutter/services.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:wakelock/wakelock.dart';