Merge remote-tracking branch 'origin/main' into claude/refactor-room-package-YQ22H
# Conflicts: # amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt
This commit is contained in:
+15
@@ -53,11 +53,13 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestBridge
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestBridge
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,6 +144,19 @@ fun CreateNestSheet(
|
|||||||
onValueChange = viewModel::onImageUrlChange,
|
onValueChange = viewModel::onImageUrlChange,
|
||||||
label = { Text(stringRes(R.string.nest_create_field_image)) },
|
label = { Text(stringRes(R.string.nest_create_field_image)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
leadingIcon = {
|
||||||
|
SelectSingleFromGallery(
|
||||||
|
isUploading = state.isUploadingImage,
|
||||||
|
tint = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.padding(start = 5.dp),
|
||||||
|
) { media ->
|
||||||
|
viewModel.uploadForImage(
|
||||||
|
uri = media,
|
||||||
|
context = context,
|
||||||
|
onError = accountViewModel.toastManager::toast,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+110
@@ -20,9 +20,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.service.uploads.CompressorQuality
|
||||||
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
|
import com.vitorpamplona.amethyst.service.uploads.MetadataStripper
|
||||||
|
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||||
|
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.image
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.image
|
||||||
@@ -36,6 +48,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backing ViewModel for [CreateNestSheet]. Holds form state, runs
|
* Backing ViewModel for [CreateNestSheet]. Holds form state, runs
|
||||||
@@ -80,6 +93,102 @@ class CreateNestViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun onImageUrlChange(value: String) = _state.update { it.copy(imageUrl = value.trim(), error = null) }
|
fun onImageUrlChange(value: String) = _state.update { it.copy(imageUrl = value.trim(), error = null) }
|
||||||
|
|
||||||
|
fun uploadForImage(
|
||||||
|
uri: SelectedMedia,
|
||||||
|
context: Context,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
) {
|
||||||
|
val avm = account ?: return
|
||||||
|
avm.launchSigner {
|
||||||
|
upload(
|
||||||
|
galleryUri = uri,
|
||||||
|
account = avm.account,
|
||||||
|
context = context,
|
||||||
|
onError = onError,
|
||||||
|
)?.let { url ->
|
||||||
|
_state.update { it.copy(imageUrl = url, error = null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun upload(
|
||||||
|
galleryUri: SelectedMedia,
|
||||||
|
account: Account,
|
||||||
|
context: Context,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
): String? {
|
||||||
|
_state.update { it.copy(isUploadingImage = true) }
|
||||||
|
try {
|
||||||
|
val strippingResult =
|
||||||
|
if (account.settings.stripLocationOnUpload) {
|
||||||
|
MetadataStripper.strip(galleryUri.uri, galleryUri.mimeType, context.applicationContext)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceUri =
|
||||||
|
if (account.settings.stripLocationOnUpload &&
|
||||||
|
strippingResult != null &&
|
||||||
|
!strippingResult.stripped
|
||||||
|
) {
|
||||||
|
onError(
|
||||||
|
stringRes(context, R.string.metadata_strip_failed_title),
|
||||||
|
stringRes(context, R.string.metadata_strip_failed_upload_cancelled),
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
strippingResult?.uri ?: galleryUri.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
val compResult = MediaCompressor().compress(sourceUri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val result =
|
||||||
|
if (account.settings.defaultFileServer.type == ServerType.NIP96) {
|
||||||
|
Nip96Uploader().upload(
|
||||||
|
uri = compResult.uri,
|
||||||
|
contentType = compResult.contentType,
|
||||||
|
size = compResult.size,
|
||||||
|
alt = null,
|
||||||
|
sensitiveContent = null,
|
||||||
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
|
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads,
|
||||||
|
onProgress = {},
|
||||||
|
httpAuth = account::createHTTPAuthorization,
|
||||||
|
context = context,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
BlossomUploader().upload(
|
||||||
|
uri = compResult.uri,
|
||||||
|
contentType = compResult.contentType,
|
||||||
|
size = compResult.size,
|
||||||
|
alt = null,
|
||||||
|
sensitiveContent = null,
|
||||||
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
|
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads,
|
||||||
|
httpAuth = account::createBlossomUploadAuth,
|
||||||
|
context = context,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.url == null) {
|
||||||
|
onError(
|
||||||
|
stringRes(context, R.string.failed_to_upload_media_no_details),
|
||||||
|
stringRes(context, R.string.server_did_not_provide_a_url_after_uploading),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.url
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e is CancellationException) throw e
|
||||||
|
onError(stringRes(context, R.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_state.update { it.copy(isUploadingImage = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle scheduled-vs-now mode. When [scheduled] is true the
|
* Toggle scheduled-vs-now mode. When [scheduled] is true the
|
||||||
* sheet shows a date / time picker and the published event uses
|
* sheet shows a date / time picker and the published event uses
|
||||||
@@ -200,6 +309,7 @@ class CreateNestViewModel : ViewModel() {
|
|||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
val isPublishing: Boolean,
|
val isPublishing: Boolean,
|
||||||
val error: String?,
|
val error: String?,
|
||||||
|
val isUploadingImage: Boolean = false,
|
||||||
/** When true, publish as `status=PLANNED` + `["starts", <unix>]`. */
|
/** When true, publish as `status=PLANNED` + `["starts", <unix>]`. */
|
||||||
val scheduled: Boolean = false,
|
val scheduled: Boolean = false,
|
||||||
/** Unix seconds of the scheduled start; 0 = not yet picked. */
|
/** Unix seconds of the scheduled start; 0 = not yet picked. */
|
||||||
|
|||||||
Reference in New Issue
Block a user