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