mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor: share-intent ports for the composers
- SharedMediaResolver (rememberSharedMediaResolver) replaces the app's resolveSharedMedia(context, …); OnIncomingShare replaces the per-screen SEND-intent listeners in ShortNotePostScreen and GenericCommentPostScreen. - ViewModels log their class via this::class.simpleName. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
-63
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.ui.actions.uploads
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Resolves a shared content-URI string (from an Android SEND intent) into a
|
||||
* [SelectedMedia] by reading its MIME type off the main thread. Returns null for
|
||||
* a null/blank URI. Shared by the share-to-compose pre-fill paths (DM chatroom,
|
||||
* group DM, …) so the URI→MIME→SelectedMedia logic lives in one place.
|
||||
*/
|
||||
suspend fun resolveSharedMedia(
|
||||
context: Context,
|
||||
uriString: String?,
|
||||
): SelectedMedia? =
|
||||
uriString?.ifBlank { null }?.toUri()?.let { uri ->
|
||||
withContext(Dispatchers.IO) {
|
||||
SelectedMedia(uri, context.contentResolver.getType(uri))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch form for SEND_MULTIPLE shares: resolves every URI in one trip to the IO dispatcher
|
||||
* instead of one context switch per file. Blank entries are dropped, so an empty list in means
|
||||
* an empty list out and the composer stays closed.
|
||||
*/
|
||||
suspend fun resolveSharedMedia(
|
||||
context: Context,
|
||||
uriStrings: List<String>,
|
||||
): ImmutableList<SelectedMedia> {
|
||||
val uris = uriStrings.mapNotNull { it.ifBlank { null }?.toUri() }
|
||||
if (uris.isEmpty()) return persistentListOf()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
uris.map { SelectedMedia(it, context.contentResolver.getType(it)) }.toImmutableList()
|
||||
}
|
||||
}
|
||||
+10
-45
@@ -20,8 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.nip22Comments
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -46,20 +44,14 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
@@ -79,10 +71,12 @@ import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind
|
||||
import com.vitorpamplona.amethyst.commons.resources.zapraiser
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.OnIncomingShare
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePictureButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideoButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
@@ -114,7 +108,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePicker
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest
|
||||
@@ -132,9 +125,6 @@ import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun ReplyCommentPostScreen(
|
||||
@@ -149,7 +139,7 @@ fun ReplyCommentPostScreen(
|
||||
val postViewModel: CommentPostViewModel = viewModel()
|
||||
postViewModel.init(accountViewModel)
|
||||
|
||||
val context = LocalContext.current
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
|
||||
LaunchedEffect(postViewModel, accountViewModel) {
|
||||
replyId?.let { LocalCache.getNoteIfExists(it) }?.let {
|
||||
@@ -165,11 +155,8 @@ fun ReplyCommentPostScreen(
|
||||
postViewModel.message.setTextAndPlaceCursorAtEnd(it)
|
||||
postViewModel.onMessageChanged()
|
||||
}
|
||||
attachment?.ifBlank { null }?.toUri()?.let {
|
||||
withContext(Dispatchers.IO) {
|
||||
val mediaType = context.contentResolver.getType(it)
|
||||
postViewModel.selectImage(persistentListOf(SelectedMedia(it, mediaType)))
|
||||
}
|
||||
mediaResolver.resolve(attachment)?.let {
|
||||
postViewModel.selectImage(persistentListOf(it))
|
||||
}
|
||||
if (draftId == null) {
|
||||
postViewModel.applySignature()
|
||||
@@ -188,32 +175,11 @@ fun GenericCommentPostScreen(
|
||||
) {
|
||||
WatchAndLoadMyEmojiList(accountViewModel)
|
||||
|
||||
val context = LocalContext.current
|
||||
val activity = context.getActivity()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
DisposableEffect(nav, activity) {
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.ifBlank { null }?.let {
|
||||
postViewModel.addToMessage(it)
|
||||
}
|
||||
|
||||
// Use the `intent` parameter (the new intent), not activity.intent (the launch intent).
|
||||
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_STREAM, Uri::class.java)?.let { uri ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val mediaType = context.contentResolver.getType(uri)
|
||||
postViewModel.selectImage(persistentListOf(SelectedMedia(uri, mediaType)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
OnIncomingShare(
|
||||
onText = postViewModel::addToMessage,
|
||||
onMedia = { postViewModel.selectImage(persistentListOf(it)) },
|
||||
)
|
||||
|
||||
// NIP-9B: when replying into a NIP-72 community, mount the community feed
|
||||
// subscription so the latest kind:34551 rules document is fetched and
|
||||
@@ -466,7 +432,6 @@ private fun GenericCommentPostBody(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
ImageVideoDescription(
|
||||
it,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ fun BuzzWorkspaceOverflowMenu(
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
error = e.message ?: e::class.simpleName
|
||||
error = e.message ?: e.javaClass.simpleName
|
||||
} finally {
|
||||
minting = false
|
||||
}
|
||||
|
||||
+3
-4
@@ -34,7 +34,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -47,6 +46,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.event.EventFind
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.chats_history_proto_nip04
|
||||
import com.vitorpamplona.amethyst.commons.resources.chats_history_proto_nip17
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachCursor
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachDetailDialog
|
||||
@@ -60,7 +60,6 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.privateDM.hea
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.dal.ChatroomFeedViewModel
|
||||
@@ -144,10 +143,10 @@ fun ChatroomView(
|
||||
newPostModel.onMessageChanged()
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
if (attachmentUri != null) {
|
||||
LaunchedEffect(key1 = attachmentUri) {
|
||||
resolveSharedMedia(context, attachmentUri)?.let {
|
||||
mediaResolver.resolve(attachmentUri)?.let {
|
||||
newPostModel.pickedMedia(persistentListOf(it))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -823,7 +823,7 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
// NIP-04 sending is deprecated. NIP-17 is always used.
|
||||
|
||||
+1
-1
@@ -718,7 +718,7 @@ class LongFormPostViewModel :
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
override fun updateZapPercentage(
|
||||
|
||||
+1
-1
@@ -616,7 +616,7 @@ open class NewProductViewModel :
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
override fun updateZapPercentage(
|
||||
|
||||
+10
-41
@@ -20,8 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -55,13 +53,11 @@ import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -72,9 +68,6 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -110,10 +103,12 @@ import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind
|
||||
import com.vitorpamplona.amethyst.commons.resources.zapraiser
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.OnIncomingShare
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePictureButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideoButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
@@ -156,7 +151,6 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationSection
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessagePreview
|
||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePicker
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest
|
||||
@@ -177,10 +171,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
||||
@Composable
|
||||
@@ -202,8 +193,7 @@ fun ShortNotePostScreen(
|
||||
postViewModel.setGroupThread(groupThreadId, groupThreadRelayUrl)
|
||||
|
||||
val context = LocalContext.current
|
||||
val activity = context.getActivity()
|
||||
val scope = rememberCoroutineScope()
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
|
||||
val proposeAiImprovements by
|
||||
accountViewModel.settings.uiSettingsFlow.automaticallyProposeAiImprovements
|
||||
@@ -227,39 +217,19 @@ fun ShortNotePostScreen(
|
||||
postViewModel.message.setTextAndPlaceCursorAtEnd(it)
|
||||
postViewModel.onMessageChanged()
|
||||
}
|
||||
attachment?.ifBlank { null }?.toUri()?.let {
|
||||
withContext(Dispatchers.IO) {
|
||||
val mediaType = context.contentResolver.getType(it)
|
||||
postViewModel.selectImage(persistentListOf(SelectedMedia(it, mediaType)))
|
||||
}
|
||||
mediaResolver.resolve(attachment)?.let {
|
||||
postViewModel.selectImage(persistentListOf(it))
|
||||
}
|
||||
if (draftId == null && forkId == null) {
|
||||
postViewModel.applySignature()
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(nav, activity) {
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.ifBlank { null }?.let {
|
||||
postViewModel.addToMessage(it)
|
||||
}
|
||||
|
||||
// Use the `intent` parameter (the new intent), not activity.intent (the launch intent).
|
||||
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_STREAM, Uri::class.java)?.let { uri ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val mediaType = context.contentResolver.getType(uri)
|
||||
postViewModel.selectImage(persistentListOf(SelectedMedia(uri, mediaType)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
OnIncomingShare(
|
||||
onText = postViewModel::addToMessage,
|
||||
onMedia = { postViewModel.selectImage(persistentListOf(it)) },
|
||||
)
|
||||
|
||||
NewPostScreenInner(postViewModel, accountViewModel, nav)
|
||||
}
|
||||
@@ -610,7 +580,6 @@ private fun NewPostScreenBody(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
ImageVideoDescription(
|
||||
it,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
|
||||
+2
-2
@@ -1995,7 +1995,7 @@ open class ShortNotePostViewModel :
|
||||
} catch (e: Exception) {
|
||||
onError(
|
||||
uploadErrorTitle,
|
||||
loadStringRes(Res.string.upload_error_voice_message_exception, e.message ?: e.javaClass.simpleName),
|
||||
loadStringRes(Res.string.upload_error_voice_message_exception, e.message ?: e::class.simpleName.orEmpty()),
|
||||
)
|
||||
voiceRecording = null
|
||||
} finally {
|
||||
@@ -2015,7 +2015,7 @@ open class ShortNotePostViewModel :
|
||||
override fun onCleared() {
|
||||
writingAssistant?.close()
|
||||
writingAssistant = null
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
override fun updateZapPercentage(
|
||||
|
||||
+1
-1
@@ -326,6 +326,6 @@ class VoiceReplyViewModel : ViewModel() {
|
||||
|
||||
override fun onCleared() {
|
||||
cancel()
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -604,7 +604,7 @@ open class NestNewMessageViewModel :
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
fun updateZapPercentage(
|
||||
|
||||
+1
-1
@@ -555,7 +555,7 @@ private fun broadcastWithDiagnostics(
|
||||
} catch (ce: CancellationException) {
|
||||
throw ce
|
||||
} catch (e: Throwable) {
|
||||
val klass = e::class.simpleName ?: "Throwable"
|
||||
val klass = e.javaClass.simpleName ?: "Throwable"
|
||||
val msg = e.message?.takeIf { it.isNotBlank() } ?: "no message"
|
||||
accountViewModel.toastManager.toast(failedTitle, "$klass: $msg")
|
||||
}
|
||||
|
||||
+1
-1
@@ -670,7 +670,7 @@ class NewPublicMessageViewModel :
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" }
|
||||
Log.d("Init") { "OnCleared: ${this::class.simpleName}" }
|
||||
}
|
||||
|
||||
override fun updateZapPercentage(
|
||||
|
||||
+3
-4
@@ -41,7 +41,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.upload_image
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -62,7 +62,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaView
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -102,9 +101,9 @@ fun NewPictureButton(
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
LaunchedEffect(sharedAttachments) {
|
||||
resolveSharedMedia(context, sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
mediaResolver.resolve(sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
}
|
||||
|
||||
if (wantsToPostFromCamera) {
|
||||
|
||||
+3
-4
@@ -41,7 +41,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -55,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.resources.upload_image
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideo
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -63,7 +63,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaView
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -103,9 +102,9 @@ fun NewShortVideoButton(
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
LaunchedEffect(sharedAttachments) {
|
||||
resolveSharedMedia(context, sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
mediaResolver.resolve(sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
}
|
||||
|
||||
if (wantsToRecordVideo) {
|
||||
|
||||
+3
-4
@@ -41,7 +41,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -56,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideo
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMediaView
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -110,9 +109,9 @@ fun NewVideoFeedButton(
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val mediaResolver = rememberSharedMediaResolver()
|
||||
LaunchedEffect(sharedAttachments) {
|
||||
resolveSharedMedia(context, sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
mediaResolver.resolve(sharedAttachments).takeIf { it.isNotEmpty() }?.let { pickedURIs = it }
|
||||
}
|
||||
|
||||
if (wantsToPostFromCamera) {
|
||||
|
||||
+1
-1
@@ -279,7 +279,7 @@ class HlsPublishOrchestrator(
|
||||
// scope; NewHlsVideoViewModel.cancel() calls reset() to put state back to Idle.
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
_state.value = HlsPublishState.Failure(message = e.message ?: e::class.simpleName.orEmpty())
|
||||
_state.value = HlsPublishState.Failure(message = e.message ?: e.javaClass.simpleName.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.commons.ui.actions.uploads
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.util.Consumer
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private class ContentResolverSharedMedia(
|
||||
private val context: Context,
|
||||
) : SharedMediaResolver {
|
||||
override suspend fun resolve(uriString: String?): SelectedMedia? =
|
||||
uriString?.ifBlank { null }?.toUri()?.let { uri ->
|
||||
withContext(Dispatchers.IO) {
|
||||
SelectedMedia(uri, context.contentResolver.getType(uri))
|
||||
}
|
||||
}
|
||||
|
||||
// One trip to the IO dispatcher for the whole batch instead of one context switch per file.
|
||||
override suspend fun resolve(uriStrings: List<String>): ImmutableList<SelectedMedia> {
|
||||
val uris = uriStrings.mapNotNull { it.ifBlank { null }?.toUri() }
|
||||
if (uris.isEmpty()) return persistentListOf()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
uris.map { SelectedMedia(it, context.contentResolver.getType(it)) }.toImmutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberSharedMediaResolver(): SharedMediaResolver {
|
||||
val context = LocalContext.current.applicationContext
|
||||
return remember(context) { ContentResolverSharedMedia(context) }
|
||||
}
|
||||
|
||||
private tailrec fun Context.findComponentActivity(): ComponentActivity? =
|
||||
when (this) {
|
||||
is ComponentActivity -> this
|
||||
is ContextWrapper -> baseContext.findComponentActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun OnIncomingShare(
|
||||
onText: (String) -> Unit,
|
||||
onMedia: (SelectedMedia) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = context.findComponentActivity() ?: return
|
||||
val scope = rememberCoroutineScope()
|
||||
val currentOnText by rememberUpdatedState(onText)
|
||||
val currentOnMedia by rememberUpdatedState(onMedia)
|
||||
|
||||
DisposableEffect(activity) {
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.ifBlank { null }?.let {
|
||||
currentOnText(it)
|
||||
}
|
||||
|
||||
// Use the `intent` parameter (the new intent), not activity.intent (the launch intent).
|
||||
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_STREAM, Uri::class.java)?.let { uri ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val mediaType = context.contentResolver.getType(uri)
|
||||
currentOnMedia(SelectedMedia(uri, mediaType))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.commons.ui.actions.uploads
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Turns the file addresses another app shared with this one (an Android SEND intent's
|
||||
* `content://` URIs) into [SelectedMedia], reading each MIME type off the main thread.
|
||||
*/
|
||||
interface SharedMediaResolver {
|
||||
/** Null for a null or blank address. */
|
||||
suspend fun resolve(uriString: String?): SelectedMedia?
|
||||
|
||||
/** Batch form for multi-file shares; blank entries are dropped. */
|
||||
suspend fun resolve(uriStrings: List<String>): ImmutableList<SelectedMedia>
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun rememberSharedMediaResolver(): SharedMediaResolver
|
||||
|
||||
/**
|
||||
* Calls [onText] and [onMedia] for content shared into the app while this screen is showing (a
|
||||
* keyboard sending a GIF as a new share, say). Android listens for new SEND intents; the other
|
||||
* platforms receive none yet.
|
||||
*/
|
||||
@Composable
|
||||
expect fun OnIncomingShare(
|
||||
onText: (String) -> Unit,
|
||||
onMedia: (SelectedMedia) -> Unit,
|
||||
)
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.commons.ui.actions.uploads
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.StringMediaUri
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
// Addresses are plain paths or URLs here; nothing reports a MIME type for them yet.
|
||||
private object StringSharedMedia : SharedMediaResolver {
|
||||
override suspend fun resolve(uriString: String?): SelectedMedia? = uriString?.ifBlank { null }?.let { SelectedMedia(StringMediaUri(it), null) }
|
||||
|
||||
override suspend fun resolve(uriStrings: List<String>): ImmutableList<SelectedMedia> = uriStrings.mapNotNull { resolve(it) }.toImmutableList()
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberSharedMediaResolver(): SharedMediaResolver = StringSharedMedia
|
||||
|
||||
/** No app shares into this one on this platform yet. */
|
||||
@Composable
|
||||
actual fun OnIncomingShare(
|
||||
onText: (String) -> Unit,
|
||||
onMedia: (SelectedMedia) -> Unit,
|
||||
) = Unit
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.commons.ui.actions.uploads
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.commons.service.uploads.StringMediaUri
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
// Addresses are plain paths or URLs here; nothing reports a MIME type for them yet.
|
||||
private object StringSharedMedia : SharedMediaResolver {
|
||||
override suspend fun resolve(uriString: String?): SelectedMedia? = uriString?.ifBlank { null }?.let { SelectedMedia(StringMediaUri(it), null) }
|
||||
|
||||
override suspend fun resolve(uriStrings: List<String>): ImmutableList<SelectedMedia> = uriStrings.mapNotNull { resolve(it) }.toImmutableList()
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberSharedMediaResolver(): SharedMediaResolver = StringSharedMedia
|
||||
|
||||
/** No app shares into this one on this platform yet. */
|
||||
@Composable
|
||||
actual fun OnIncomingShare(
|
||||
onText: (String) -> Unit,
|
||||
onMedia: (SelectedMedia) -> Unit,
|
||||
) = Unit
|
||||
Reference in New Issue
Block a user