diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/controls/RenderControlButtons.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/controls/RenderControlButtons.kt
index aab7ff6e9..5964bf660 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/controls/RenderControlButtons.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/controls/RenderControlButtons.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
+import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
@@ -71,11 +72,11 @@ fun RenderControlButtons(
}
AnimatedShareButton(controllerVisible, buttonPositionModifier.padding(end = Size165dp)) { popupExpanded, toggle ->
- ShareMediaAction(accountViewModel = accountViewModel, popupExpanded, mediaData.videoUri, mediaData.callbackUri, null, null, null, mediaData.mimeType, toggle)
+ ShareMediaAction(accountViewModel = accountViewModel, popupExpanded, mediaData.videoUri, mediaData.callbackUri, null, null, null, mediaData.mimeType, toggle, content = MediaUrlVideo(url = mediaData.videoUri, mimeType = mediaData.mimeType, artworkUri = mediaData.artworkUri, authorName = mediaData.authorName, description = mediaData.title, uri = mediaData.callbackUri))
}
} else {
AnimatedShareButton(controllerVisible, buttonPositionModifier.padding(end = Size110dp)) { popupExpanded, toggle ->
- ShareMediaAction(accountViewModel = accountViewModel, popupExpanded, mediaData.videoUri, mediaData.callbackUri, null, null, null, mediaData.mimeType, toggle)
+ ShareMediaAction(accountViewModel = accountViewModel, popupExpanded, mediaData.videoUri, mediaData.callbackUri, null, null, null, mediaData.mimeType, toggle, content = MediaUrlVideo(url = mediaData.videoUri, mimeType = mediaData.mimeType, artworkUri = mediaData.artworkUri, authorName = mediaData.authorName, description = mediaData.title, uri = mediaData.callbackUri))
}
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index 97d8e193d..720539ac6 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -53,7 +53,6 @@ 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
import androidx.compose.ui.Modifier
@@ -69,6 +68,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
+import androidx.lifecycle.viewModelScope
import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter
import coil3.compose.SubcomposeAsyncImage
@@ -246,8 +246,8 @@ fun LocalImageView(
}
val ratio = remember(content) { content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.localFile.toString()) }
- CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) {
- if (it) {
+ CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { imageVisible ->
+ if (imageVisible) {
SubcomposeAsyncImage(
model = content.localFile,
contentDescription = content.description,
@@ -552,11 +552,7 @@ fun ShowHash(content: MediaUrlContent) {
}
@Composable
-fun WaitAndDisplay(
- content:
- @Composable()
- (AnimatedVisibilityScope.() -> Unit),
-) {
+fun WaitAndDisplay(content: @Composable (AnimatedVisibilityScope.() -> Unit)) {
val visible = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
@@ -733,7 +729,7 @@ fun ShareMediaAction(
onDismiss: () -> Unit,
content: BaseMediaContent? = null,
) {
- val scope = rememberCoroutineScope()
+ val scope = accountViewModel.viewModelScope
// Track if video is downloading - hoisted here to block menu dismiss during download
val isDownloadingVideo = remember { mutableStateOf(false) }
@@ -939,7 +935,11 @@ private suspend fun shareVideoFile(
// GlobalScope is intentional: cleanup must survive after share UI is dismissed.
GlobalScope.launch(Dispatchers.IO) {
delay(SHARED_VIDEO_CLEANUP_DELAY_MS)
- sharedFile?.delete()
+ sharedFile?.let { file ->
+ if (!file.delete()) {
+ Log.w("ZoomableContentView", "Failed to delete shared file: ${file.path}")
+ }
+ }
}
withContext(Dispatchers.Main) {
@@ -950,8 +950,14 @@ private suspend fun shareVideoFile(
Log.w("ZoomableContentView", "Failed to share video: $videoUrl", e)
// Clean up temp file on error
- tempFile.delete()
- sharedFile?.delete()
+ if (!tempFile.delete()) {
+ Log.w("ZoomableContentView", "Failed to delete temp file: ${tempFile.path}")
+ }
+ sharedFile?.let { file ->
+ if (!file.delete()) {
+ Log.w("ZoomableContentView", "Failed to delete shared file: ${file.path}")
+ }
+ }
withContext(Dispatchers.Main) {
Toast
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt
index 5e4fc7b20..0be396cda 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt
@@ -128,6 +128,8 @@ import kotlinx.collections.immutable.toImmutableList
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
+private const val HTTPS_PREFIX = "https://"
+
@Composable
fun RelayInformationScreen(
relayUrl: String,
@@ -434,7 +436,7 @@ fun FeesCard(
}
payUrl?.let {
val uri = LocalUriHandler.current
- ClickableInfoRow(Icons.Default.Payment, stringRes(R.string.payments_url), it.removePrefix("https://")) {
+ ClickableInfoRow(Icons.Default.Payment, stringRes(R.string.payments_url), it.removePrefix(HTTPS_PREFIX)) {
runCatching {
uri.openUri(it)
}
@@ -583,8 +585,8 @@ fun SoftwareCard(relayInfo: Nip11RelayInformation) {
Column(modifier = Modifier.padding(16.dp)) {
val uri = LocalUriHandler.current
relayInfo.software?.let {
- if (it.contains("https://")) {
- ClickableInfoRow(Icons.Default.Code, stringRes(R.string.software), it.removePrefix("git+https://").removePrefix("https://")) {
+ if (it.contains(HTTPS_PREFIX)) {
+ ClickableInfoRow(Icons.Default.Code, stringRes(R.string.software), it.removePrefix("git+https://").removePrefix(HTTPS_PREFIX)) {
runCatching {
uri.openUri(it.removePrefix("git+"))
}
@@ -809,7 +811,7 @@ fun PoliciesCard(relay: Nip11RelayInformation) {
val pp = relay.privacy_policy
if (pp != null) {
- ClickableInfoRow(Icons.Default.PrivacyTip, stringRes(R.string.privacy_policy), pp.removePrefix("https://")) {
+ ClickableInfoRow(Icons.Default.PrivacyTip, stringRes(R.string.privacy_policy), pp.removePrefix(HTTPS_PREFIX)) {
runCatching {
uri.openUri(pp)
}
@@ -820,7 +822,7 @@ fun PoliciesCard(relay: Nip11RelayInformation) {
val ts = relay.terms_of_service
if (ts != null) {
- ClickableInfoRow(Icons.Default.Gavel, stringRes(R.string.terms_and_conditions), ts.removePrefix("https://")) {
+ ClickableInfoRow(Icons.Default.Gavel, stringRes(R.string.terms_and_conditions), ts.removePrefix(HTTPS_PREFIX)) {
runCatching {
uri.openUri(ts)
}
diff --git a/amethyst/src/main/res/values-ca-rES/strings.xml b/amethyst/src/main/res/values-ca-rES/strings.xml
new file mode 100644
index 000000000..66a570486
--- /dev/null
+++ b/amethyst/src/main/res/values-ca-rES/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/amethyst/src/main/res/values-de-rDE/strings.xml b/amethyst/src/main/res/values-de-rDE/strings.xml
index 9015e0965..77c479819 100644
--- a/amethyst/src/main/res/values-de-rDE/strings.xml
+++ b/amethyst/src/main/res/values-de-rDE/strings.xml
@@ -1202,6 +1202,9 @@ anz der Bedingungen ist erforderlich
Das Relais, mit dem sich alle Benutzer dieses Chats verbinden
Bild teilen…
Bild kann nicht geteilt werden, bitte versuchen Sie es später erneut…
+ Video teilen…
+ Video kann nicht geteilt werden, bitte versuchen Sie es später erneut…
+ Video wird heruntergeladen…
Suche Hashtag: #%1$s
Nicht übersetzen von
Die hier angezeigten Sprachen werden nicht übersetzt. Wählen Sie eine Sprache, um sie zu entfernen und lassen Sie sie erneut übersetzen.
diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml
index dece24a0f..5c4b548ec 100644
--- a/amethyst/src/main/res/values-hu-rHU/strings.xml
+++ b/amethyst/src/main/res/values-hu-rHU/strings.xml
@@ -1202,6 +1202,9 @@
Az átjátszó, amelyhez a csevegés összes felhasználója csatlakozik
Kép megosztása…
Nem lehetett megosztani a képet, próbálja meg újra később…
+ Videó megosztása…
+ Nem sikerült megosztani a videót, próbálja meg újra később…
+ Videó letöltése…
Hashtag keresése: #%1$s
Innentől NE fordítsa le
Az itt látható nyelvek nem lesznek lefordítva. Az eltávolításához és az újbóli fordításhoz válasszon ki egy nyelvet.
diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml
index eaf7cce96..557f88dc0 100644
--- a/amethyst/src/main/res/values-pl-rPL/strings.xml
+++ b/amethyst/src/main/res/values-pl-rPL/strings.xml
@@ -499,7 +499,7 @@
Nie
Lista obserwowanych
Obserwowane
- Obserwujące
+ Obserwowane kontakty
Domyślna lista obserwowanych
Obserwuje przez proxy
W pobliżu
@@ -1199,6 +1199,9 @@
Transmiter, z którym łączą się wszyscy użytkownicy tego czatu
Udostępnij zdjęcie…
Nie można udostępnić obrazu, spróbuj ponownie później…
+ Udostępnij film…
+ Nie można udostępnić filmu, spróbuj ponownie później…
+ Pobieranie filmu…
Szukaj tagu: #%1$s
Nie tłumacz z
Języki wyświetlane tutaj nie będą tłumaczone. Wybierz język, aby usunąć go z listy języków nietłumaczonych.
diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml
index a90421341..09aa6def8 100644
--- a/amethyst/src/main/res/values-pt-rBR/strings.xml
+++ b/amethyst/src/main/res/values-pt-rBR/strings.xml
@@ -1197,6 +1197,9 @@
O relé a qual todos os usuários deste chat se conectam
Compartilhar imagem…
Não é possível compartilhar a imagem, por favor tente novamente mais tarde…
+ Compartilhar vídeo…
+ Não foi possível compartilhar o vídeo, tente novamente mais tarde…
+ Baixando vídeo…
Pesquisar hashtag: #%1$s
Não Traduzir de
Os idiomas mostrados aqui não serão traduzidos. Selecione um idioma para removê-lo e traduzi-lo novamente.
diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml
index 44571cb2a..e8be7b477 100644
--- a/amethyst/src/main/res/values-sv-rSE/strings.xml
+++ b/amethyst/src/main/res/values-sv-rSE/strings.xml
@@ -1196,6 +1196,9 @@
Reläet som alla användare av den här chatten ansluter till
Dela bild…
Kunde inte dela bilden, försök igen senare…
+ Dela video…
+ Det gick inte att dela videon, försök igen senare…
+ Laddar ner videon…
Sök hashtag: #%1$s
Översätt inte från
Språk som visas här kommer inte att översättas. Välj ett språk för att ta bort det och få det översatt igen.
diff --git a/amethyst/src/main/res/values-zh-rCN/strings.xml b/amethyst/src/main/res/values-zh-rCN/strings.xml
index 7f427f586..23e488f30 100644
--- a/amethyst/src/main/res/values-zh-rCN/strings.xml
+++ b/amethyst/src/main/res/values-zh-rCN/strings.xml
@@ -1202,6 +1202,9 @@
此聊天所有用户都连接到的中继
分享图片…
无法分享图片,请稍后重试…
+ 分享视频…
+ 无法分享视频,请稍后重试…
+ 正下载视频…
搜索话题标签:#%1$s
不要翻译
此处显示的语言不会被翻译,请选择一种目标语言重新翻译并去除这个提示。
diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt
index 556dda5de..618491580 100644
--- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt
+++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt
@@ -52,15 +52,15 @@ class ManualFilterDeserializer {
}
return Filter(
- ids = jsonObject.get("ids").mapNotNull { it.asTextOrNull() },
- authors = jsonObject.get("authors").mapNotNull { it.asTextOrNull() },
- kinds = jsonObject.get("kinds").mapNotNull { it.asIntOrNull() },
- tags = tagsIn.associateWith { jsonObject.get(it).mapNotNull { it.asTextOrNull() } },
- tagsAll = tagsAll.associateWith { jsonObject.get(it).mapNotNull { it.asTextOrNull() } },
- since = jsonObject.get("since").asLongOrNull(),
- until = jsonObject.get("until").asLongOrNull(),
- limit = jsonObject.get("limit").asIntOrNull(),
- search = jsonObject.get("search").asTextOrNull(),
+ ids = jsonObject.get("ids")?.mapNotNull { it.asTextOrNull() },
+ authors = jsonObject.get("authors")?.mapNotNull { it.asTextOrNull() },
+ kinds = jsonObject.get("kinds")?.mapNotNull { it.asIntOrNull() },
+ tags = tagsIn.associateWith { jsonObject.get("#$it").mapNotNull { it.asTextOrNull() } },
+ tagsAll = tagsAll.associateWith { jsonObject.get("&$it").mapNotNull { it.asTextOrNull() } },
+ since = jsonObject.get("since")?.asLongOrNull(),
+ until = jsonObject.get("until")?.asLongOrNull(),
+ limit = jsonObject.get("limit")?.asIntOrNull(),
+ search = jsonObject.get("search")?.asTextOrNull(),
)
}
}
diff --git a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapperTest.kt b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapperTest.kt
index b9cc65956..b9b732596 100644
--- a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapperTest.kt
+++ b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapperTest.kt
@@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.jackson
import com.vitorpamplona.quartz.nip01Core.core.Event
+import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
@@ -152,4 +153,28 @@ class JacksonMapperTest {
assertContentEquals(tag, deserialized.tags[index])
}
}
+
+ @Test
+ fun shouldNotThrowExceptionWhenDeserializingEmptyFilter() {
+ val json = Filter().toJson()
+ val deserialized = JacksonMapper.fromJsonTo(json)
+
+ assertEquals(null, deserialized.ids)
+ }
+
+ @Test
+ fun shouldNotThrowExceptionWhenDeserializingFilterTags() {
+ val expectedTagValue = "3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da"
+ val json =
+ Filter(
+ tags = mapOf("p" to listOf(expectedTagValue)),
+ tagsAll = mapOf("p" to listOf(expectedTagValue)),
+ ).toJson()
+ val deserialized = JacksonMapper.fromJsonTo(json)
+
+ assertEquals(true, deserialized.tags?.keys?.contains("p"))
+ assertEquals(listOf(expectedTagValue), deserialized.tags?.get("p"))
+ assertEquals(true, deserialized.tagsAll?.keys?.contains("p"))
+ assertEquals(listOf(expectedTagValue), deserialized.tagsAll?.get("p"))
+ }
}