From bdd37b22803ff810a0862f8552b46ded9ea81779 Mon Sep 17 00:00:00 2001 From: Faisal Date: Mon, 30 Aug 2021 17:40:43 -0500 Subject: [PATCH] added content provider --- .../android/app/src/main/AndroidManifest.xml | 35 +++++++++- .../kotlin/com/example/dmedia/MainActivity.kt | 64 +++++++++++++++++-- .../app/src/main/res/xml/provider_paths.xml | 3 + mobile/lib/controllers/home.dart | 60 +++++++++++++++-- mobile/lib/views/home.dart | 7 +- .../main/kotlin/org/altlimit/saf/SafPlugin.kt | 49 +++++++------- 6 files changed, 179 insertions(+), 39 deletions(-) create mode 100644 mobile/android/app/src/main/res/xml/provider_paths.xml diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index dd2683f..1ca184b 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -3,8 +3,17 @@ + + + - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/com/example/dmedia/MainActivity.kt b/mobile/android/app/src/main/kotlin/com/example/dmedia/MainActivity.kt index 3c0e6d8..e087a66 100644 --- a/mobile/android/app/src/main/kotlin/com/example/dmedia/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/com/example/dmedia/MainActivity.kt @@ -1,29 +1,81 @@ package com.example.dmedia import android.util.Log +import android.content.Intent +import android.content.ClipData +import android.os.Bundle import androidx.annotation.NonNull +import androidx.core.content.FileProvider import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel +import java.io.File class MainActivity: FlutterActivity() { - private val LOGTAG = "DMEDIA"; + private val LOGTAG = "DMEDIA" private val CHANNEL = "org.altlimit.dmedia/native" + private var intentAction: String? = null override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> - if (call.method == "nativeMethod") { - log("Called: " + call.method) - } else { - result.notImplemented() + when (call.method) { + "getIntentAction" -> + result.success(intentAction) + "setResult" -> { + val path: String? = call.argument("path") + val paths: Array? = call.argument("paths") + if (path != null || paths != null) { + try { + val intent = Intent() + val context = getApplicationContext(); + val provider = context.getPackageName() + ".provider" + if (paths != null) { + val clipData = ClipData.newRawUri(null, FileProvider.getUriForFile(context, provider, File(path))) + for (i in 1 until paths.size) + clipData.addItem(ClipData.Item(FileProvider.getUriForFile(context, provider, File(paths[i])))) + intent.setClipData(clipData) + } else + intent.setData(FileProvider.getUriForFile(context, provider, File(path))) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setResult(RESULT_OK, intent) + } catch (e: Exception) { + result.error("failed", "Error: $e", null) + setResult(RESULT_CANCELED) + } + finish() + } + result.success(null) + } + else -> result.notImplemented() } } } + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + updateIntentAction(getIntent()) + log("onCreate: IntentAction $intentAction") + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + updateIntentAction(intent) + log("onNewIntent: IntentAction $intentAction") + } + + private fun updateIntentAction(intent: Intent) { + val intent = getIntent() + intentAction = intent.getAction() + if (intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)) + intentAction += "|MULTIPLE" + if (intentAction != null && intent.getType() != null) + intentAction += "|" + intent.getType(); + } + private fun log(message: String) { - Log.d(LOGTAG, message); + Log.d(LOGTAG, message) } } diff --git a/mobile/android/app/src/main/res/xml/provider_paths.xml b/mobile/android/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000..3225f3a --- /dev/null +++ b/mobile/android/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/mobile/lib/controllers/home.dart b/mobile/lib/controllers/home.dart index 5ad6176..c1f4225 100644 --- a/mobile/lib/controllers/home.dart +++ b/mobile/lib/controllers/home.dart @@ -7,27 +7,35 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:share_plus/share_plus.dart'; import 'package:dmedia/util.dart'; -class HomeController extends GetxController { +class HomeController extends GetxController with WidgetsBindingObserver { late StreamSubscription intentSub; final tabIndex = 0.obs; final loadedMedia = [].obs; final selectedIndex = 0.obs; final selectedIndexes = {}.obs; final multiSelect = false.obs; + final isGetContent = false.obs; + final allowMultiple = false.obs; int page = 1; int? pages; final refreshIndicatorKey = new GlobalKey(); - final List tabs = [ + final List tabOptions = [ TabElement('Gallery', Icons.photo, 'gallery'), // TabElement('Albums', Icons.photo_album, 'albums'), TabElement('Trash', Icons.delete_outline, 'trash'), ]; late ScrollController scrollController; + String? intentAction; + + List get tabs { + return tabOptions; + } @override void onInit() { super.onInit(); + updateIntentAction(); scrollController = ScrollController(); scrollController.addListener(() async { if (scrollController.offset >= @@ -83,6 +91,27 @@ class HomeController extends GetxController { super.dispose(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + updateIntentAction(); + } + print('State: ' + state.toString()); + } + + Future updateIntentAction() async { + intentAction = await Util.nativeCall('getIntentAction'); + isGetContent(intentAction != null && + (intentAction!.startsWith('android.intent.action.GET_CONTENT') || + intentAction!.startsWith('android.intent.action.PICK'))); + print('IntentAction: $intentAction'); + if (isGetContent.value) { + allowMultiple(intentAction!.contains('|MULTIPLE')); + Get.reset(); + onTabTapped(0); + } + } + TabElement get currentTab { return tabs[tabIndex.value]; } @@ -202,7 +231,12 @@ class HomeController extends GetxController { return media.getSharePath(); })); done(); - await Share.shareFiles(files); + if (isGetContent.value) + Util.nativeCall('setResult', + files.length == 1 ? {'path': files[0]} : {'paths': files}); + else + await Share.shareFiles(files); + multiSelect(false); selectedIndexes.clear(); } @@ -223,16 +257,34 @@ class HomeController extends GetxController { Get.toNamed('/settings'); } - onMediaItemTap(int index) { + onMediaItemTap(int index) async { + if (isGetContent.value) { + final Media media = loadedMedia[index]; + if (intentAction!.contains('|video/') && !media.isVideo) { + Util.showMessage(Get.context!, 'Select a video'); + return; + } else if (intentAction!.contains('|image/') && !media.isImage) { + Util.showMessage(Get.context!, 'Select a photo'); + return; + } + } if (multiSelect.value) { toggleSelected(index); return; } + if (isGetContent.value) { + selectedIndexes.addAll({index: true}); + await shareSelectedTap(); + return; + } selectedIndex(index); Get.toNamed('/media'); } onMediaLongPress(int index) { + if (isGetContent.value && !allowMultiple.value) { + return; + } multiSelect(true); toggleSelected(index); diff --git a/mobile/lib/views/home.dart b/mobile/lib/views/home.dart index 82cffad..24a3580 100644 --- a/mobile/lib/views/home.dart +++ b/mobile/lib/views/home.dart @@ -16,7 +16,12 @@ class HomeView extends StatelessWidget { slivers: [ Obx(() => SliverAppBar( actions: [ - if (c.multiSelect.value) ...[ + if (c.isGetContent.value && c.multiSelect.value) + IconButton( + icon: Icon(Icons.check), + onPressed: c.shareSelectedTap, + ), + if (!c.isGetContent.value && c.multiSelect.value) ...[ if (c.currentTab.key == 'trash') IconButton( icon: Icon(Icons.undo), diff --git a/saf/android/src/main/kotlin/org/altlimit/saf/SafPlugin.kt b/saf/android/src/main/kotlin/org/altlimit/saf/SafPlugin.kt index f01d557..c9c1792 100644 --- a/saf/android/src/main/kotlin/org/altlimit/saf/SafPlugin.kt +++ b/saf/android/src/main/kotlin/org/altlimit/saf/SafPlugin.kt @@ -2,8 +2,8 @@ package org.altlimit.saf import androidx.annotation.NonNull -import android.app.Activity; -import android.content.Context; +import android.app.Activity +import android.content.Context import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -11,7 +11,7 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugin.common.PluginRegistry import android.util.Log import android.os.Bundle @@ -34,12 +34,12 @@ class SafPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry private lateinit var context: Context private lateinit var activity: Activity private lateinit var activityBinding: ActivityPluginBinding - private var storageResult: MethodChannel.Result? = null; + private var storageResult: MethodChannel.Result? = null - private val LOGTAG = "SAF"; + private val LOGTAG = "SAF" private fun log(message: String) { - Log.d(LOGTAG, message); + Log.d(LOGTAG, message) } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { @@ -50,17 +50,17 @@ class SafPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "removeFile") { - val path: String? = call.argument("path"); + val path: String? = call.argument("path") if (path != null) - result.success(removeFile(path)); + result.success(removeFile(path)) else - result.error("path error", "path not provided", null); + result.error("path error", "path not provided", null) } else if (call.method == "folderPicker") { if (storageResult != null) - storageResult?.success(null); - storageResult = null; - storageHelper.openFolderPicker(); - storageResult = result; + storageResult?.success(null) + storageResult = null + storageHelper.openFolderPicker() + storageResult = result } else { result.notImplemented() } @@ -71,51 +71,48 @@ class SafPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry } override fun onDetachedFromActivity() { - activityBinding.removeActivityResultListener(this); - activityBinding.removeOnSaveStateListener(this); + activityBinding.removeActivityResultListener(this) + activityBinding.removeOnSaveStateListener(this) } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { } override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.getActivity(); + activity = binding.getActivity() activityBinding = binding storageHelper = SimpleStorageHelper(activity, REQUEST_CODE_STORAGE_ACCESS, null) storageHelper.onFolderSelected = { _, folder -> - storageResult?.success(folder.getAbsolutePath(context)); - storageResult = null; + storageResult?.success(folder.getAbsolutePath(context)) + storageResult = null } - binding.addActivityResultListener(this); - binding.addOnSaveStateListener(this); + binding.addActivityResultListener(this) + binding.addOnSaveStateListener(this) } override fun onDetachedFromActivityForConfigChanges() { } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Boolean { - // super.onActivityResult(requestCode, resultCode, data) storageHelper.storage.onActivityResult(requestCode, resultCode, data) return true } override fun onSaveInstanceState(outState: Bundle) { storageHelper.onSaveInstanceState(outState) - // super.onSaveInstanceState(outState) } override fun onRestoreInstanceState(savedInstanceState: Bundle?) { - // super.onRestoreInstanceState(savedInstanceState) if (savedInstanceState != null) storageHelper.onRestoreInstanceState(savedInstanceState) } private fun removeFile(path: String): Boolean { - val file = DocumentFileCompat.fromFullPath(context, path, requiresWriteAccess = true); + val file = DocumentFileCompat.fromFullPath(context, path, requiresWriteAccess = true) if (file != null) { - return file.delete(); + return file.delete() } - return false; + return false } }