feat(android): Implement sharing internal files and text
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user