mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-09 20:02:36 +02:00
Makes Amethyst a share target for texts, images and videos.
This commit is contained in:
@@ -103,6 +103,24 @@
|
|||||||
<data android:scheme="nostr+walletconnect" />
|
<data android:scheme="nostr+walletconnect" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:label="New Post">
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:label="New Post">
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:label="New Post">
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="" />
|
android:value="" />
|
||||||
|
@@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst.ui.navigation
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@@ -291,6 +293,11 @@ fun AppNavigation(
|
|||||||
popEnterTransition = { scaleIn },
|
popEnterTransition = { scaleIn },
|
||||||
popExitTransition = { slideOutVerticallyToBottom },
|
popExitTransition = { slideOutVerticallyToBottom },
|
||||||
) {
|
) {
|
||||||
|
val draftMessage = it.message()
|
||||||
|
val attachment =
|
||||||
|
it.arguments?.getString("attachment")?.ifBlank { null }?.let {
|
||||||
|
Uri.parse(it)
|
||||||
|
}
|
||||||
val baseReplyTo = it.arguments?.getString("baseReplyTo")
|
val baseReplyTo = it.arguments?.getString("baseReplyTo")
|
||||||
val quote = it.arguments?.getString("quote")
|
val quote = it.arguments?.getString("quote")
|
||||||
val fork = it.arguments?.getString("fork")
|
val fork = it.arguments?.getString("fork")
|
||||||
@@ -298,6 +305,8 @@ fun AppNavigation(
|
|||||||
val draft = it.arguments?.getString("draft")
|
val draft = it.arguments?.getString("draft")
|
||||||
val enableMessageInterface = it.arguments?.getBoolean("enableMessageInterface") ?: false
|
val enableMessageInterface = it.arguments?.getBoolean("enableMessageInterface") ?: false
|
||||||
NewPostScreen(
|
NewPostScreen(
|
||||||
|
message = draftMessage,
|
||||||
|
attachment = attachment,
|
||||||
baseReplyTo = baseReplyTo?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
baseReplyTo = baseReplyTo?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||||
quote = quote?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
quote = quote?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||||
fork = fork?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
fork = fork?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||||
@@ -326,6 +335,15 @@ private fun NavigateIfIntentRequested(
|
|||||||
val activity = LocalContext.current.getActivity()
|
val activity = LocalContext.current.getActivity()
|
||||||
var newAccount by remember { mutableStateOf<String?>(null) }
|
var newAccount by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
if (activity.intent.action == Intent.ACTION_SEND) {
|
||||||
|
activity.intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
|
nav.newStack(buildNewPostRoute(draftMessage = it))
|
||||||
|
}
|
||||||
|
|
||||||
|
(activity.intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
|
||||||
|
nav.newStack(buildNewPostRoute(attachment = it))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
var currentIntentNextPage by remember {
|
var currentIntentNextPage by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
activity.intent
|
activity.intent
|
||||||
@@ -373,6 +391,15 @@ private fun NavigateIfIntentRequested(
|
|||||||
DisposableEffect(nav, activity) {
|
DisposableEffect(nav, activity) {
|
||||||
val consumer =
|
val consumer =
|
||||||
Consumer<Intent> { intent ->
|
Consumer<Intent> { intent ->
|
||||||
|
if (intent.action == Intent.ACTION_SEND) {
|
||||||
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
|
nav.newStack(buildNewPostRoute(draftMessage = it))
|
||||||
|
}
|
||||||
|
|
||||||
|
(intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
|
||||||
|
nav.newStack(buildNewPostRoute(attachment = it))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val uri = intent.data?.toString()
|
val uri = intent.data?.toString()
|
||||||
if (!uri.isNullOrBlank()) {
|
if (!uri.isNullOrBlank()) {
|
||||||
// navigation functions
|
// navigation functions
|
||||||
@@ -400,6 +427,7 @@ private fun NavigateIfIntentRequested(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
activity.addOnNewIntentListener(consumer)
|
activity.addOnNewIntentListener(consumer)
|
||||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||||
}
|
}
|
||||||
@@ -408,6 +436,7 @@ private fun NavigateIfIntentRequested(
|
|||||||
AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null }
|
AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.getActivity(): MainActivity {
|
fun Context.getActivity(): MainActivity {
|
||||||
if (this is MainActivity) return this
|
if (this is MainActivity) return this
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.navigation
|
package com.vitorpamplona.amethyst.ui.navigation
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
@@ -39,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class Route(
|
sealed class Route(
|
||||||
@@ -223,10 +225,12 @@ sealed class Route(
|
|||||||
|
|
||||||
object NewPost :
|
object NewPost :
|
||||||
Route(
|
Route(
|
||||||
route = "NewPost?baseReplyTo={baseReplyTo}"e={quote}&fork={fork}&version={version}&draft={draft}&enableMessageInterface={enableMessageInterface}",
|
route = "NewPost?message={message}&attachment={attachment}&baseReplyTo={baseReplyTo}"e={quote}&fork={fork}&version={version}&draft={draft}&enableMessageInterface={enableMessageInterface}",
|
||||||
icon = R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments =
|
arguments =
|
||||||
listOf(
|
listOf(
|
||||||
|
navArgument("message") { type = NavType.StringType },
|
||||||
|
navArgument("attachment") { type = NavType.StringType },
|
||||||
navArgument("baseReplyTo") { type = NavType.StringType },
|
navArgument("baseReplyTo") { type = NavType.StringType },
|
||||||
navArgument("quote") { type = NavType.StringType },
|
navArgument("quote") { type = NavType.StringType },
|
||||||
navArgument("fork") { type = NavType.StringType },
|
navArgument("fork") { type = NavType.StringType },
|
||||||
@@ -287,6 +291,8 @@ private fun getRouteWithArguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun buildNewPostRoute(
|
fun buildNewPostRoute(
|
||||||
|
draftMessage: String? = null,
|
||||||
|
attachment: Uri? = null,
|
||||||
baseReplyTo: String? = null,
|
baseReplyTo: String? = null,
|
||||||
quote: String? = null,
|
quote: String? = null,
|
||||||
fork: String? = null,
|
fork: String? = null,
|
||||||
@@ -295,6 +301,8 @@ fun buildNewPostRoute(
|
|||||||
enableMessageInterface: Boolean = false,
|
enableMessageInterface: Boolean = false,
|
||||||
): String =
|
): String =
|
||||||
"NewPost?" +
|
"NewPost?" +
|
||||||
|
"message=${draftMessage?.let { URLEncoder.encode(it, "utf-8") } ?: ""}&" +
|
||||||
|
"attachment=${attachment?.let { URLEncoder.encode(it.toString(), "utf-8") } ?: ""}&" +
|
||||||
"baseReplyTo=${baseReplyTo ?: ""}&" +
|
"baseReplyTo=${baseReplyTo ?: ""}&" +
|
||||||
"quote=${quote ?: ""}&" +
|
"quote=${quote ?: ""}&" +
|
||||||
"fork=${fork ?: ""}&" +
|
"fork=${fork ?: ""}&" +
|
||||||
|
@@ -194,6 +194,8 @@ import java.lang.Math.round
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPostScreen(
|
fun NewPostScreen(
|
||||||
|
message: String? = null,
|
||||||
|
attachment: Uri? = null,
|
||||||
baseReplyTo: Note? = null,
|
baseReplyTo: Note? = null,
|
||||||
quote: Note? = null,
|
quote: Note? = null,
|
||||||
fork: Note? = null,
|
fork: Note? = null,
|
||||||
@@ -231,6 +233,12 @@ fun NewPostScreen(
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft)
|
postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft)
|
||||||
|
message?.ifBlank { null }?.let {
|
||||||
|
postViewModel.updateMessage(TextFieldValue(it))
|
||||||
|
}
|
||||||
|
attachment?.let {
|
||||||
|
postViewModel.selectImage(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,16 +326,16 @@ fun NewPostScreen(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxSize().padding(
|
Modifier.fillMaxSize(),
|
||||||
start = Size10dp,
|
|
||||||
end = Size10dp,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.padding(
|
||||||
|
start = Size10dp,
|
||||||
|
end = Size10dp,
|
||||||
|
).weight(1f),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
|
@@ -387,7 +387,7 @@ fun PrepareChatroomViewModels(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (draftMessage != null) {
|
if (draftMessage != null) {
|
||||||
LaunchedEffect(key1 = draftMessage) { newPostModel.message = TextFieldValue(draftMessage) }
|
LaunchedEffect(key1 = draftMessage) { newPostModel.updateMessage(TextFieldValue(draftMessage)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatroomScreen(
|
ChatroomScreen(
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst
|
||||||
|
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
class UrlDecoderTest {
|
||||||
|
val uri = "content://com.google.android.apps.photos.contentprovider/0/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F1000023553/REQUIRE_ORIGINAL/NONE/image%2Fjpeg/913263593"
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRecursiveDecoding() {
|
||||||
|
val encoded = URLEncoder.encode(uri, "utf-8")
|
||||||
|
assertEquals("content%3A%2F%2Fcom.google.android.apps.photos.contentprovider%2F0%2F1%2Fcontent%253A%252F%252Fmedia%252Fexternal%252Fimages%252Fmedia%252F1000023553%2FREQUIRE_ORIGINAL%2FNONE%2Fimage%252Fjpeg%2F913263593", encoded)
|
||||||
|
|
||||||
|
val decoded = URLDecoder.decode(encoded, "utf-8")
|
||||||
|
assertEquals(uri, decoded)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user