feat(android): Implement sharing internal files and text

This commit is contained in:
2023-09-18 17:58:16 +02:00
parent f971a0e078
commit 44187675c7
14 changed files with 891 additions and 633 deletions

View File

@@ -4,7 +4,7 @@
<application>
<provider
android:name="org.moxxy.moxxy_native.content.MoxxyFileProvider"
android:authorities="org.moxxy.moxxyv2.fileprovider"
android:authorities="org.moxxy.moxxyv2.fileprovider2"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

View File

@@ -24,7 +24,7 @@ const val NOTIFICATION_EXTRA_ID_KEY = "notification_id"
const val NOTIFICATION_MESSAGE_EXTRA_MIME = "mime"
const val NOTIFICATION_MESSAGE_EXTRA_PATH = "path"
const val MOXXY_FILEPROVIDER_ID = "org.moxxy.moxxyv2.fileprovider"
const val MOXXY_FILEPROVIDER_ID = "org.moxxy.moxxyv2.fileprovider2"
// Shared preferences keys
const val SHARED_PREFERENCES_KEY = "org.moxxy.moxxyv2"

View File

@@ -1,6 +1,24 @@
package org.moxxy.moxxy_native.content
import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import org.moxxy.moxxy_native.MOXXY_FILEPROVIDER_ID
import org.moxxy.moxxy_native.R
import java.io.File
class MoxxyFileProvider : FileProvider(R.xml.file_paths)
class MoxxyFileProvider : FileProvider(R.xml.file_paths) {
companion object {
/*
* Convert a path @path inside a sharable storage directory into a content URI, given
* the application's context @context.
* */
fun getUriForPath(context: Context, path: String): Uri {
return getUriForFile(
context,
MOXXY_FILEPROVIDER_ID,
File(path),
)
}
}
}

View File

@@ -14,10 +14,8 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.app.TaskStackBuilder
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.IconCompat
import org.moxxy.moxxy_native.MARK_AS_READ_ACTION
import org.moxxy.moxxy_native.MOXXY_FILEPROVIDER_ID
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_ID_KEY
import org.moxxy.moxxy_native.NOTIFICATION_EXTRA_JID_KEY
import org.moxxy.moxxy_native.NOTIFICATION_MESSAGE_EXTRA_MIME
@@ -27,7 +25,7 @@ import org.moxxy.moxxy_native.REPLY_ACTION
import org.moxxy.moxxy_native.REPLY_TEXT_KEY
import org.moxxy.moxxy_native.TAG
import org.moxxy.moxxy_native.TAP_ACTION
import java.io.File
import org.moxxy.moxxy_native.content.MoxxyFileProvider
class NotificationsImplementation(private val context: Context) : MoxxyNotificationsApi {
override fun createNotificationGroups(groups: List<NotificationGroup>) {
@@ -207,11 +205,7 @@ class NotificationsImplementation(private val context: Context) : MoxxyNotificat
)
// If we got an image, turn it into a content URI and set it
if (message.content.mime != null && message.content.path != null) {
val fileUri = FileProvider.getUriForFile(
context,
MOXXY_FILEPROVIDER_ID,
File(message.content.path),
)
val fileUri = MoxxyFileProvider.getUriForPath(context, message.content.path)
msg.apply {
setData(message.content.mime, fileUri)

View File

@@ -8,6 +8,8 @@ import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
@@ -41,17 +43,66 @@ class FlutterError(
val details: Any? = null,
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class ShareItem(
val path: String? = null,
val mime: String,
val text: String? = null,
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): ShareItem {
val path = list[0] as String?
val mime = list[1] as String
val text = list[2] as String?
return ShareItem(path, mime, text)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
path,
mime,
text,
)
}
}
@Suppress("UNCHECKED_CAST")
private object MoxxyPlatformApiCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
128.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareItem.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ShareItem -> {
stream.write(128)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface MoxxyPlatformApi {
fun getPersistentDataPath(): String
fun getCacheDataPath(): String
fun openBatteryOptimisationSettings()
fun isIgnoringBatteryOptimizations(): Boolean
fun shareItems(items: List<ShareItem>, genericMimeType: String)
companion object {
/** The codec used by MoxxyPlatformApi. */
val codec: MessageCodec<Any?> by lazy {
StandardMessageCodec()
MoxxyPlatformApiCodec
}
/** Sets up an instance of `MoxxyPlatformApi` to handle messages through the `binaryMessenger`. */
@@ -122,6 +173,26 @@ interface MoxxyPlatformApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.moxxy_native.MoxxyPlatformApi.shareItems", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val itemsArg = args[0] as List<ShareItem>
val genericMimeTypeArg = args[1] as String
var wrapped: List<Any?>
try {
api.shareItems(itemsArg, genericMimeTypeArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View File

@@ -5,6 +5,8 @@ import android.content.Intent
import android.net.Uri
import android.os.PowerManager
import android.provider.Settings
import androidx.core.app.ShareCompat
import org.moxxy.moxxy_native.content.MoxxyFileProvider
class PlatformImplementation(private val context: Context) : MoxxyPlatformApi {
override fun getPersistentDataPath(): String {
@@ -27,4 +29,28 @@ class PlatformImplementation(private val context: Context) : MoxxyPlatformApi {
val pm = context.getSystemService(PowerManager::class.java)
return pm.isIgnoringBatteryOptimizations(context.packageName)
}
override fun shareItems(items: List<ShareItem>, genericMimeType: String) {
// Empty lists make no sense
assert(items.isNotEmpty())
// Convert the paths to content URIs
val builder = ShareCompat.IntentBuilder(context).setType(genericMimeType)
for (item in items) {
assert(item.text == null && item.path != null || item.text != null && item.path == null)
if (item.text != null) {
builder.setText(item.text)
} else if (item.path != null) {
builder.addStream(MoxxyFileProvider.getUriForPath(context, item.path))
}
}
// We cannot just use startChooser() because then Android complains that we're not attached
// to an Activity. So, we just ask it to start a new one.
val intent = builder.createChooserIntent().apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}