- Hide privacy toggle when video compression is selected (compression

already handles metadata stripping)
- Pass stripMetadata=false when toggle is hidden for compressed video
- Hide compression quality for audio and other non-compressible media types
- refactor
This commit is contained in:
davotoula
2026-03-18 13:52:05 +01:00
parent 20ab105e44
commit 127079ede3
18 changed files with 262 additions and 321 deletions
@@ -252,11 +252,7 @@ class MetadataStripper {
return StrippingResult(uri, false)
}
val extension =
when {
primaryMime.contains("mp4a") || primaryMime.contains("aac") -> ".m4a"
else -> ".mp4"
}
val extension = ".m4a"
tempOutputFile = File.createTempFile("stripped_audio_", extension, context.cacheDir)
muxer = MediaMuxer(tempOutputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
@@ -321,41 +317,61 @@ class MetadataStripper {
}
} ?: return StrippingResult(uri, false)
val bytes = tempInputFile.readBytes()
val fileSize = tempInputFile.length().toInt()
var startOffset = 0
var endOffset = bytes.size
var endOffset = fileSize
// Strip ID3v2 header (at beginning of file)
if (bytes.size >= 10 &&
bytes[0] == 'I'.code.toByte() &&
bytes[1] == 'D'.code.toByte() &&
bytes[2] == '3'.code.toByte()
// Read first 10 bytes to check for ID3v2 header
val header = ByteArray(10)
tempInputFile.inputStream().use { it.read(header) }
if (fileSize >= 10 &&
header[0] == 'I'.code.toByte() &&
header[1] == 'D'.code.toByte() &&
header[2] == '3'.code.toByte()
) {
val size =
(bytes[6].toInt() and 0x7F shl 21) or
(bytes[7].toInt() and 0x7F shl 14) or
(bytes[8].toInt() and 0x7F shl 7) or
(bytes[9].toInt() and 0x7F)
(header[6].toInt() and 0x7F shl 21) or
(header[7].toInt() and 0x7F shl 14) or
(header[8].toInt() and 0x7F shl 7) or
(header[9].toInt() and 0x7F)
startOffset = 10 + size
}
// Strip ID3v1 tag (last 128 bytes)
if (endOffset - startOffset >= 128 &&
bytes[endOffset - 128] == 'T'.code.toByte() &&
bytes[endOffset - 127] == 'A'.code.toByte() &&
bytes[endOffset - 126] == 'G'.code.toByte()
) {
endOffset -= 128
// Read last 128 bytes to check for ID3v1 tag
if (endOffset - startOffset >= 128) {
val tail = ByteArray(128)
java.io.RandomAccessFile(tempInputFile, "r").use { raf ->
raf.seek((endOffset - 128).toLong())
raf.readFully(tail)
}
if (tail[0] == 'T'.code.toByte() &&
tail[1] == 'A'.code.toByte() &&
tail[2] == 'G'.code.toByte()
) {
endOffset -= 128
}
}
if (startOffset == 0 && endOffset == bytes.size) {
if (startOffset == 0 && endOffset == fileSize) {
tempInputFile.delete()
return StrippingResult(uri, true) // no tags found, already clean
}
val tempOutputFile = File.createTempFile("stripped_mp3_", ".mp3", context.cacheDir)
tempOutputFile.outputStream().use { output ->
output.write(bytes, startOffset, endOffset - startOffset)
java.io.RandomAccessFile(tempInputFile, "r").use { raf ->
raf.seek(startOffset.toLong())
tempOutputFile.outputStream().use { output ->
val buffer = ByteArray(8192)
var remaining = endOffset - startOffset
while (remaining > 0) {
val toRead = minOf(buffer.size, remaining)
val read = raf.read(buffer, 0, toRead)
if (read <= 0) break
output.write(buffer, 0, read)
remaining -= read
}
}
}
tempInputFile.delete()
@@ -39,8 +39,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CurrencyBitcoin
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -127,23 +125,7 @@ fun EditPostView(
postViewModel.load(edit, versionLookingAt)
}
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
Dialog(
onDismissRequest = { onClose() },
@@ -96,6 +96,21 @@ open class EditPostViewModel : ViewModel() {
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
@@ -203,21 +218,7 @@ open class EditPostViewModel : ViewModel() {
context,
useH265Codec,
stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -65,6 +65,21 @@ open class NewMediaModel : ViewModel() {
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by mutableIntStateOf(1)
@@ -126,21 +141,7 @@ open class NewMediaModel : ViewModel() {
context,
useH265Codec,
stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -32,8 +32,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
@@ -91,23 +89,7 @@ fun NewMediaView(
postViewModel.load(account, uris)
}
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
Dialog(
onDismissRequest = { onClose() },
@@ -0,0 +1,50 @@
/*
* 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
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.uploads.StrippingFailureState
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
fun StrippingFailureDialog(dialogState: StrippingFailureState?) {
dialogState ?: return
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
@@ -250,47 +250,52 @@ fun ImageVideoDescription(
)
}
SettingSwitchItem(
title = R.string.strip_metadata_label,
description = R.string.strip_metadata_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = stripMetadata,
onCheckedChange = { stripMetadata = it },
)
// Hide privacy toggle when video compression is selected (compression already strips metadata)
val isVideoWithCompression =
uris.first().media.isVideo() == true && mediaQualitySlider != 3
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
.padding(vertical = 8.dp),
) {
Column(
modifier = Modifier.weight(1.0f),
verticalArrangement = Arrangement.spacedBy(Size5dp),
) {
Text(
text = stringRes(R.string.media_compression_quality_label),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(R.string.media_compression_quality_explainer),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 5,
overflow = TextOverflow.Ellipsis,
)
}
if (!isVideoWithCompression) {
SettingSwitchItem(
title = R.string.strip_metadata_label,
description = R.string.strip_metadata_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = stripMetadata,
onCheckedChange = { stripMetadata = it },
)
}
val firstMedia = uris.first().media
if (firstMedia.isVideo() == true || firstMedia.isImage() == true) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
.padding(vertical = 8.dp),
) {
Column(
modifier = Modifier.weight(1.0f),
verticalArrangement = Arrangement.spacedBy(Size5dp),
) {
Text(
text = stringRes(R.string.media_compression_quality_label),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(R.string.media_compression_quality_explainer),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 5,
overflow = TextOverflow.Ellipsis,
)
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
@@ -334,7 +339,10 @@ fun ImageVideoDescription(
.fillMaxWidth()
.padding(vertical = 10.dp),
enabled = !isUploading,
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec, stripMetadata) },
onClick = {
val effectiveStripMetadata = if (isVideoWithCompression) false else stripMetadata
onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec, effectiveStripMetadata)
},
shape = QuoteBorder,
colors =
ButtonDefaults.buttonColors(
@@ -168,6 +168,21 @@ open class CommentPostViewModel :
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// Invoices
var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false)
@@ -509,21 +524,7 @@ open class CommentPostViewModel :
account,
context,
stripMetadata = stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -34,13 +34,10 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment.Companion.CenterVertically
@@ -51,6 +48,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
@@ -143,23 +141,7 @@ fun GenericCommentPostScreen(
) {
WatchAndLoadMyEmojiList(accountViewModel)
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
BackHandler {
accountViewModel.launchSigner {
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send
import android.R.attr.maxLines
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -42,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
@@ -118,23 +118,7 @@ fun PrivateMessageEditFieldRow(
}
}
channelScreenModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
TextButton(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
TextButton(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(channelScreenModel.strippingFailureDialog)
channelScreenModel.encryptedUploadErrorTitle?.let { title ->
EncryptedUploadErrorDialog(
@@ -136,6 +136,21 @@ open class ChannelNewMessageViewModel :
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
val iMetaAttachments = IMetaAttachments()
var nip95attachments by mutableStateOf<List<Pair<FileStorageEvent, FileStorageHeaderEvent>>>(emptyList())
@@ -356,21 +371,7 @@ open class ChannelNewMessageViewModel :
account,
context,
stripMetadata = uploadState.stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -26,8 +26,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -39,6 +37,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
@@ -72,23 +71,7 @@ fun EditFieldRow(
nav.popBack()
}
channelScreenModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(channelScreenModel.strippingFailureDialog)
channelScreenModel.replyTo.value?.let {
DisplayReplyingToNote(it, accountViewModel, nav) {
@@ -33,13 +33,10 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment.Companion.CenterVertically
@@ -50,6 +47,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
@@ -140,23 +138,7 @@ fun NewProductScreen(
) {
WatchAndLoadMyEmojiList(accountViewModel)
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
BackHandler {
accountViewModel.launchSigner {
@@ -152,6 +152,21 @@ open class NewProductViewModel :
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// Classifieds
var title by mutableStateOf(TextFieldValue(""))
var price by mutableStateOf(TextFieldValue(""))
@@ -417,21 +432,7 @@ open class NewProductViewModel :
myAccount,
context,
stripMetadata = stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -39,8 +39,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Poll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -48,7 +46,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -63,6 +60,7 @@ import androidx.core.util.Consumer
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordVoiceButton
@@ -182,23 +180,7 @@ private fun NewPostScreenInner(
) {
WatchAndLoadMyEmojiList(accountViewModel)
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
BackHandler {
accountViewModel.launchSigner {
@@ -197,6 +197,21 @@ open class ShortNotePostViewModel :
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// Voice Messages
var voiceRecording by mutableStateOf<RecordingResult?>(null)
var voiceLocalFile by mutableStateOf<java.io.File?>(null)
@@ -852,21 +867,7 @@ open class ShortNotePostViewModel :
context,
useH265,
stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {
@@ -33,8 +33,6 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
@@ -58,6 +56,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
@@ -133,23 +132,7 @@ fun NewPublicMessageScreen(
WatchAndLoadMyEmojiList(accountViewModel)
postViewModel.strippingFailureDialog?.let { dialogState ->
AlertDialog(
onDismissRequest = { dialogState.onCancel() },
title = { Text(stringRes(R.string.metadata_strip_failed_title)) },
text = { Text(stringRes(R.string.metadata_strip_failed_body)) },
confirmButton = {
Button(onClick = { dialogState.onConfirm() }) {
Text(stringRes(R.string.metadata_strip_failed_upload))
}
},
dismissButton = {
Button(onClick = { dialogState.onCancel() }) {
Text(stringRes(R.string.cancel))
}
},
)
}
StrippingFailureDialog(postViewModel.strippingFailureDialog)
BackHandler {
accountViewModel.launchSigner {
@@ -170,6 +170,21 @@ class NewPublicMessageViewModel :
var strippingFailureDialog by mutableStateOf<StrippingFailureState?>(null)
private set
private suspend fun showStrippingFailureDialog(): Boolean =
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
// Invoices
var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false)
@@ -461,21 +476,7 @@ class NewPublicMessageViewModel :
account,
context,
stripMetadata = stripMetadata,
onStrippingFailed = {
suspendCancellableCoroutine { continuation ->
strippingFailureDialog =
StrippingFailureState(
onConfirm = {
strippingFailureDialog = null
continuation.resume(true) {}
},
onCancel = {
strippingFailureDialog = null
continuation.resume(false) {}
},
)
}
},
onStrippingFailed = ::showStrippingFailureDialog,
)
if (results.allGood) {