Skip to content

Commit

Permalink
added content provider
Browse files Browse the repository at this point in the history
  • Loading branch information
faisalraja committed Aug 30, 2021
1 parent 7c0aa2c commit bdd37b2
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 39 deletions.
35 changes: 33 additions & 2 deletions mobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
<application
android:label="D-Media"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
Expand Down Expand Up @@ -52,7 +61,29 @@
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>? = 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)
}
}
3 changes: 3 additions & 0 deletions mobile/android/app/src/main/res/xml/provider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<paths>
<cache-path name="cache_files" path="." />
</paths>
60 changes: 56 additions & 4 deletions mobile/lib/controllers/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<RefreshIndicatorState>();
final List<TabElement> tabs = [
final List<TabElement> tabOptions = [
TabElement('Gallery', Icons.photo, 'gallery'),
// TabElement('Albums', Icons.photo_album, 'albums'),
TabElement('Trash', Icons.delete_outline, 'trash'),
];
late ScrollController scrollController;
String? intentAction;

List<TabElement> get tabs {
return tabOptions;
}

@override
void onInit() {
super.onInit();

updateIntentAction();
scrollController = ScrollController();
scrollController.addListener(() async {
if (scrollController.offset >=
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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);

Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/views/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ class HomeView extends StatelessWidget {
slivers: <Widget>[
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),
Expand Down
49 changes: 23 additions & 26 deletions saf/android/src/main/kotlin/org/altlimit/saf/SafPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ 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
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
Expand All @@ -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) {
Expand All @@ -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()
}
Expand All @@ -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
}
}

0 comments on commit bdd37b2

Please sign in to comment.