diff --git a/.gitignore b/.gitignore index b5af05773e..de1c06cf0b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ /captures .cxx +# superpowers skill +.superpowers +docs/brainstorms +docs/superpowers # Built application files *.apk diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt index 79987c74d9..9e603f514b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt @@ -171,7 +171,9 @@ object MetadataStripper { if (muxerStarted) runCatching { muxer?.stop() } muxer?.release() extractor.release() - if (!succeeded) outputFile.delete() + if (!succeeded && !outputFile.delete()) { + Log.w("MetadataStripper", "Failed to delete temp file: ${outputFile.absolutePath}") + } } return succeeded } @@ -199,7 +201,9 @@ object MetadataStripper { val inputStream = context.contentResolver.openInputStream(uri) ?: run { - tempFile.delete() + if (!tempFile.delete()) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempFile.absolutePath}") + } return StrippingResult(uri, false) } inputStream.use { input -> @@ -218,7 +222,9 @@ object MetadataStripper { StrippingResult(tempFile.toUri(), true) } catch (e: Exception) { if (e is CancellationException) throw e - tempFile?.delete() + if (tempFile?.delete() == false) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempFile.absolutePath}") + } Log.d("MetadataStripper", "Failed to strip image metadata: ${e.message}") StrippingResult(uri, false) } @@ -304,7 +310,9 @@ object MetadataStripper { input.copyTo(output) } } ?: run { - tempInputFile.delete() + if (!tempInputFile.delete()) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempInputFile.absolutePath}") + } return StrippingResult(uri, false) } @@ -345,7 +353,9 @@ object MetadataStripper { } if (startOffset == 0L && endOffset == fileSize) { - tempInputFile.delete() + if (!tempInputFile.delete()) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempInputFile.absolutePath}") + } tempInputFile = null return StrippingResult(uri, true) // no tags found, already clean } @@ -365,14 +375,18 @@ object MetadataStripper { } } } - tempInputFile.delete() + if (!tempInputFile.delete()) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempInputFile.absolutePath}") + } tempInputFile = null Log.d("MetadataStripper", "Stripped ID3 tags from MP3") StrippingResult(tempOutputFile.toUri(), true) } catch (e: Exception) { if (e is CancellationException) throw e - tempInputFile?.delete() + if (tempInputFile?.delete() == false) { + Log.w("MetadataStripper", "Failed to delete temp file: ${tempInputFile.absolutePath}") + } Log.d("MetadataStripper", "Failed to strip MP3 metadata: ${e.message}") StrippingResult(uri, false) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt index f8522fe3fc..8d704df193 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -34,7 +33,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -61,7 +59,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Font14SP import kotlinx.collections.immutable.ImmutableList @@ -159,13 +156,11 @@ private fun BaseTextSpinner( ) } - if (optionsShowing) { - options.isNotEmpty().also { - SpinnerSelectionDialog(options = options, onDismiss = { optionsShowing = false }) { - currentText = options[it].title - optionsShowing = false - onSelect(it) - } + if (optionsShowing && options.isNotEmpty()) { + SpinnerSelectionDialog(options = options, onDismiss = { optionsShowing = false }) { + currentText = options[it].title + optionsShowing = false + onSelect(it) } } } @@ -211,14 +206,14 @@ fun SpinnerSelectionDialog( ) { Dialog(onDismissRequest = onDismiss) { Surface( - border = BorderStroke(0.25.dp, Color.LightGray), - shape = RoundedCornerShape(5.dp), + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { LazyColumn { title?.let { item { Row( - modifier = Modifier.fillMaxWidth().padding(16.dp, 16.dp), + modifier = Modifier.fillMaxWidth().padding(20.dp), horizontalArrangement = Arrangement.Center, ) { Text( @@ -227,7 +222,6 @@ fun SpinnerSelectionDialog( fontWeight = FontWeight.Bold, ) } - HorizontalDivider(color = Color.LightGray, thickness = DividerThickness) } } itemsIndexed(options) { index, item -> @@ -237,7 +231,7 @@ fun SpinnerSelectionDialog( Modifier .fillMaxWidth() .clickable { onSelect(index) } - .padding(16.dp, 16.dp) + .padding(horizontal = 16.dp, vertical = 12.dp) .semantics { role = Role.Button contentDescription = optionsOfLabel @@ -245,9 +239,6 @@ fun SpinnerSelectionDialog( ) { Column { onRenderItem(item) } } - if (index < options.lastIndex) { - HorizontalDivider(color = Color.LightGray, thickness = DividerThickness) - } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt index 18e3840912..17d3d12e63 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt @@ -89,6 +89,7 @@ import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists +import com.vitorpamplona.amethyst.isDebug import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote @@ -473,14 +474,16 @@ fun ListContent( route = Route.Wallet, ) - NavigationRow( - title = R.string.route_chess, - icon = R.drawable.ic_chess, - iconReference = 1, - tint = MaterialTheme.colorScheme.onBackground, - nav = nav, - route = Route.Chess, - ) + if (isDebug) { + NavigationRow( + title = R.string.route_chess, + icon = R.drawable.ic_chess, + iconReference = 1, + tint = MaterialTheme.colorScheme.onBackground, + nav = nav, + route = Route.Chess, + ) + } NavigationRow( title = R.string.event_sync_title, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt index 62f7217670..be00e48457 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt @@ -21,20 +21,36 @@ package com.vitorpamplona.amethyst.ui.navigation.topbars import android.Manifest +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ViewList +import androidx.compose.material.icons.automirrored.outlined.VolumeOff import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.outlined.Groups +import androidx.compose.material.icons.outlined.LocationOn +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material.icons.outlined.Public +import androidx.compose.material.icons.outlined.SensorDoor import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -43,14 +59,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted @@ -62,7 +81,6 @@ import com.vitorpamplona.amethyst.model.TopFilter import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote import com.vitorpamplona.amethyst.ui.components.LoadingAnimation -import com.vitorpamplona.amethyst.ui.components.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.ui.screen.CommunityName import com.vitorpamplona.amethyst.ui.screen.FeedDefinition @@ -74,6 +92,8 @@ import com.vitorpamplona.amethyst.ui.screen.RelayName import com.vitorpamplona.amethyst.ui.screen.ResourceName import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Font12SP +import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -120,6 +140,8 @@ fun FeedFilterSpinner( stringRes(R.string.feed_filter_select_an_option, selectAnOption) } + val openDropdownLabel = stringRes(R.string.open_dropdown_menu) + Box( modifier = modifier, contentAlignment = Alignment.Center, @@ -137,7 +159,7 @@ fun FeedFilterSpinner( Text( text = stringRes(R.string.lack_location_permissions), - fontSize = 12.sp, + fontSize = Font12SP, lineHeight = 12.sp, ) } else { @@ -152,7 +174,7 @@ fun FeedFilterSpinner( Row { Text( text = "(${myLocation.geoHash})", - fontSize = 12.sp, + fontSize = Font12SP, lineHeight = 12.sp, ) Spacer(modifier = StdHorzSpacer) @@ -162,7 +184,7 @@ fun FeedFilterSpinner( ) { cityName -> Text( text = "($cityName)", - fontSize = 12.sp, + fontSize = Font12SP, lineHeight = 12.sp, ) } @@ -171,7 +193,7 @@ fun FeedFilterSpinner( LocationState.LocationResult.LackPermission -> { Text( text = stringRes(R.string.lack_location_permissions), - fontSize = 12.sp, + fontSize = Font12SP, lineHeight = 12.sp, ) } @@ -179,7 +201,7 @@ fun FeedFilterSpinner( LocationState.LocationResult.Loading -> { Text( text = stringRes(R.string.loading_location), - fontSize = 12.sp, + fontSize = Font12SP, lineHeight = 12.sp, ) } @@ -207,7 +229,7 @@ fun FeedFilterSpinner( }.semantics { role = Role.DropdownList stateDescription = accessibilityDescription - onClick(label = "Open feed filter menu") { + onClick(label = openDropdownLabel) { optionsShowing = true return@onClick true } @@ -215,20 +237,18 @@ fun FeedFilterSpinner( ) } - if (optionsShowing) { - options.isNotEmpty().also { - SpinnerSelectionDialog( - title = explainer, - options = options, - onDismiss = { optionsShowing = false }, - onSelect = { - selected = options[it] - optionsShowing = false - onSelect(it) - }, - ) { - RenderOption(it.name, accountViewModel) - } + if (optionsShowing && options.isNotEmpty()) { + GroupedFeedFilterDialog( + title = explainer, + options = options, + onDismiss = { optionsShowing = false }, + onSelect = { + selected = options[it] + optionsShowing = false + onSelect(it) + }, + ) { + RenderOption(it.name, accountViewModel) } } } @@ -241,84 +261,258 @@ fun RenderOption( when (option) { is GeoHashName -> { LoadCityName(option.geoHashTag) { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = "/g/$it", color = MaterialTheme.colorScheme.onSurface) - } + Text(text = "/g/$it", fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface) } } is HashtagName -> { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = option.name(), color = MaterialTheme.colorScheme.onSurface) - } + Text(text = option.name(), fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface) } is ResourceName -> { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = stringRes(id = option.resourceId), - color = MaterialTheme.colorScheme.onSurface, - ) - } + Text( + text = stringRes(id = option.resourceId), + fontSize = Font14SP, + color = MaterialTheme.colorScheme.onSurface, + ) } is PeopleListName -> { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - val noteState by observeNote(option.note, accountViewModel) + val noteState by observeNote(option.note, accountViewModel) - val noteEvent = noteState.note.event - val name = - when (noteEvent) { - is PeopleListEvent -> { - noteEvent.titleOrName() ?: option.note.dTag() - } - - is FollowListEvent -> { - noteEvent.title() ?: option.note.dTag() - } - - else -> { - option.note.dTag() - } + val noteEvent = noteState.note.event + val name = + when (noteEvent) { + is PeopleListEvent -> { + noteEvent.titleOrName() ?: option.note.dTag() } - Text(text = name, color = MaterialTheme.colorScheme.onSurface) - } + is FollowListEvent -> { + noteEvent.title() ?: option.note.dTag() + } + + else -> { + option.note.dTag() + } + } + + Text(text = name, fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface) } is CommunityName -> { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - val it by observeNote(option.note, accountViewModel) + val it by observeNote(option.note, accountViewModel) - Text(text = "/n/${((it.note as? AddressableNote)?.dTag() ?: "")}", color = MaterialTheme.colorScheme.onSurface) - } + Text(text = "/n/${((it.note as? AddressableNote)?.dTag() ?: "")}", fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface) } is RelayName -> { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), + Text( + text = option.name(), + fontSize = Font14SP, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } +} + +@Immutable +private data class IndexedFeedDefinition( + val originalIndex: Int, + val item: FeedDefinition, +) + +private enum class FeedGroup( + @param:androidx.annotation.StringRes val labelRes: Int, +) { + FEEDS(R.string.feed_group_feeds), + HASHTAGS(R.string.feed_group_hashtags), + COMMUNITIES(R.string.feed_group_communities), + LISTS(R.string.feed_group_lists), +} + +private fun groupFeedDefinitions(options: ImmutableList): Map> { + val indexed = options.mapIndexed { index, item -> IndexedFeedDefinition(index, item) } + return indexed.groupBy { entry -> + when (entry.item.name) { + is HashtagName -> FeedGroup.HASHTAGS + is CommunityName -> FeedGroup.COMMUNITIES + is PeopleListName -> FeedGroup.LISTS + else -> FeedGroup.FEEDS + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun GroupedFeedFilterDialog( + title: String, + options: ImmutableList, + onSelect: (Int) -> Unit, + onDismiss: () -> Unit, + onRenderItem: @Composable (FeedDefinition) -> Unit, +) { + val grouped = remember(options) { groupFeedDefinitions(options) } + + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, + ) { + LazyColumn( + modifier = Modifier.padding(vertical = 20.dp), ) { - Text( - text = option.name(), - color = MaterialTheme.colorScheme.onSurface, - ) + item { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 8.dp), + ) + } + + FeedGroup.entries.forEach { group -> + val items = grouped[group] + if (!items.isNullOrEmpty()) { + item { + GroupSection( + label = stringRes(group.labelRes), + items = items, + isChipLayout = group == FeedGroup.HASHTAGS, + onSelect = onSelect, + onRenderItem = onRenderItem, + ) + } + } + } } } } } + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun GroupSection( + label: String, + items: List, + isChipLayout: Boolean, + onSelect: (Int) -> Unit, + onRenderItem: @Composable (FeedDefinition) -> Unit, +) { + Surface( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow, + ) { + Column { + Text( + text = label.uppercase(), + fontSize = Font12SP, + letterSpacing = 0.8.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 6.dp), + ) + + if (isChipLayout) { + FlowRow( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + items.forEach { entry -> + Surface( + modifier = Modifier.clickable { onSelect(entry.originalIndex) }, + shape = RoundedCornerShape(18.dp), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), + color = Color.Transparent, + ) { + Text( + text = entry.item.name.name(), + fontSize = 13.sp, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(horizontal = 14.dp, vertical = 7.dp), + ) + } + } + } + Spacer(modifier = Modifier.height(4.dp)) + } else { + items.forEach { entry -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .clickable { onSelect(entry.originalIndex) } + .padding(horizontal = 16.dp, vertical = 6.dp), + ) { + FeedIcon( + item = entry.item, + modifier = Size20Modifier, + ) + Spacer(modifier = Modifier.padding(start = 12.dp)) + Column(modifier = Modifier.weight(1f)) { onRenderItem(entry.item) } + } + } + } + } + } +} + +@Composable +private fun FeedIcon( + item: FeedDefinition, + modifier: Modifier = Modifier, +) { + val icon = + when (item.code) { + is TopFilter.Global -> { + Icons.Outlined.Public + } + + is TopFilter.AroundMe -> { + Icons.Outlined.LocationOn + } + + is TopFilter.AllFollows -> { + Icons.Outlined.Groups + } + + is TopFilter.AllUserFollows -> { + Icons.Outlined.Person + } + + is TopFilter.DefaultFollows -> { + Icons.Outlined.Groups + } + + is TopFilter.MuteList -> { + Icons.AutoMirrored.Outlined.VolumeOff + } + + is TopFilter.Chess -> { + Icons.Outlined.Groups + } + + is TopFilter.PeopleList -> { + Icons.AutoMirrored.Outlined.ViewList + } + + else -> { + when (item.name) { + is GeoHashName -> Icons.Outlined.LocationOn + is RelayName -> Icons.Outlined.SensorDoor + is CommunityName -> Icons.Outlined.Groups + is PeopleListName -> Icons.AutoMirrored.Outlined.ViewList + else -> Icons.Outlined.Person + } + } + } + Icon( + imageVector = icon, + contentDescription = null, + modifier = modifier, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) +} diff --git a/amethyst/src/main/res/values-cs-rCZ/strings.xml b/amethyst/src/main/res/values-cs-rCZ/strings.xml index e0d5404e14..8d8314833b 100644 --- a/amethyst/src/main/res/values-cs-rCZ/strings.xml +++ b/amethyst/src/main/res/values-cs-rCZ/strings.xml @@ -634,6 +634,10 @@ %1$s satů Od %1$s pro %1$s + Odpovědět + Označit jako přečtené + Nové zprávy + Nové zapsy Upozornit: Připojit se ke konverzaci ID uživatele nebo skupiny @@ -1043,6 +1047,13 @@ Bez komprese Použít kodek H.265/HEVC Lepší kvalita při menší velikosti souboru, ale ne všechna zařízení podporují přehrávání H.265. + Odstranit soukromá metadata + Pokusí se odstranit soukromá metadata z podporovaných mediálních souborů před nahráním + Metadata nelze odstranit + Tento formát souboru nepodporuje odstranění metadat. Mohou být obsaženy soukromé informace, jako je poloha a informace o zařízení. Přesto nahrát? + Přesto nahrát + Nepodařilo se odstranit soukromá metadata z média. Nahrávání zrušeno. + Nahrávání zrušeno Upravit koncept Přihlášení pomocí QR kódu Trasa diff --git a/amethyst/src/main/res/values-de-rDE/strings.xml b/amethyst/src/main/res/values-de-rDE/strings.xml index 9072827c39..3beec73d32 100644 --- a/amethyst/src/main/res/values-de-rDE/strings.xml +++ b/amethyst/src/main/res/values-de-rDE/strings.xml @@ -639,6 +639,10 @@ anz der Bedingungen ist erforderlich %1$s Sats Von %1$s für %1$s + Antworten + Als gelesen markieren + Neue Nachrichten + Neue Zaps Benachrichtigen: Unterhaltung beitreten Benutzer- oder Gruppen-ID @@ -1048,6 +1052,13 @@ anz der Bedingungen ist erforderlich Ohne Kompression H.265/HEVC-Codec verwenden Bessere Qualität bei kleinerer Dateigröße, aber nicht alle Geräte unterstützen die H.265-Wiedergabe. + Private Metadaten entfernen + Versucht, private Metadaten aus unterstützten Mediendateien vor dem Hochladen zu entfernen + Metadaten konnten nicht entfernt werden + Dieses Dateiformat unterstützt das Entfernen von Metadaten nicht. Private Informationen wie Standort und Geräteinformationen können enthalten sein. Trotzdem hochladen? + Trotzdem hochladen + Private Metadaten konnten nicht aus der Mediendatei entfernt werden. Hochladen abgebrochen. + Hochladen abgebrochen Entwurf bearbeiten Einloggen mit QR-Code Route diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml index 2ef7720b6a..d1ebb21e20 100644 --- a/amethyst/src/main/res/values-hu-rHU/strings.xml +++ b/amethyst/src/main/res/values-hu-rHU/strings.xml @@ -638,6 +638,10 @@ %1$s satoshi Tőle: %1$s neki: %1$s + Válasz + Megjelölés olvasottként + Új üzenetek + Új zap-ek Értesítés: Csatlakozás a beszélgetéshez Felhasználó- vagy csoport-azonosító @@ -1048,6 +1052,13 @@ Tömörítetlen H.265/HEVC-kodek használata Jobb minőség kisebb fájlméret mellett, de nem minden eszköz támogatja a H.265 lejátszást. + Privát metaadatok törlése + Kísérletek a támogatott médiafájlokból a privát metaadatok eltávolítására a feltöltés előtt + A metaadatok nem távolíthatók el + Ez a fájlformátum nem támogatja a metaadatok eltávolítását. Lehet, hogy a fájl tartalmaz személyes adatokat, például hely- és eszközadatokat. Mindenképp fel akarja tölteni? + Feltöltés mindenképp + Nem sikerült eltávolítani a médiafájlokból a privát metaadatokat. Feltöltés megszakítva. + Feltölrés megszakítva Piszkozat szerkesztése Bejelentkezés QR-kóddal Útvonal @@ -1152,6 +1163,7 @@ Jó választási lehetőségek:\n - auth.nostr1.com (ingyenes)\n - inbox.nostr.wine (fizetős)\n - relay.0xchat.com (ingyenes) Adjon hozzá 1–3 átjátszót, hogy privát postafiókként szolgáljanak. A bejövő privát üzenetek átjátszóinak el kell fogadniuk bármely üzenetet bárkitől, de azok letöltését csak Ön engedélyezheti. Beállítás most + Nem találhatók a beérkező közvetlen üzenetek átjátszói. Az üzenetek kézbesítése addig nem lehetséges, amíg be nem állítja az átjátszók listáját. Keresési átjátszók Keresési átjátszók beállítása A kifejezetten a kereséshez és a felhasználói címkézéshez tervezett átjátszólista létrehozása javítani fogja ezeket az eredményeket. diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index 8c8320e98d..3dd79b99fc 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -634,6 +634,10 @@ %1$s sats De %1$s por %1$s + Responder + Marcar como lida + Novas mensagens + Novos zaps Notificar: Entrar na conversa ID do usuário ou grupo @@ -1043,6 +1047,13 @@ Sem compressão Usar codec H.265/HEVC Melhor qualidade em arquivos menores, mas nem todos os dispositivos suportam reprodução em H.265. + Remover metadados privados + Tenta remover metadados privados de arquivos de mídia compatíveis antes do envio + Não foi possível remover os metadados + Este formato de arquivo não suporta a remoção de metadados. Informações privadas como localização e dados do dispositivo podem estar incluídas. Enviar mesmo assim? + Enviar mesmo assim + Não foi possível remover metadados privados da mídia. Envio cancelado. + Envio cancelado Editar rascunho Entrar com Código QR Rota diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index 08fc1286ad..cbfe22168f 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -633,6 +633,10 @@ %1$s sats Från %1$s till %1$s + Svara + Markera som läst + Nya meddelanden + Nya zaps Meddela: Gå med i konversation Användare eller grupp ID @@ -1042,6 +1046,13 @@ Okomprimerad Använd H.265/HEVC-codec Bättre kvalitet med mindre filstorlek, men inte alla enheter stöder H.265-uppspelning. + Ta bort privat metadata + Försöker ta bort privat metadata från mediefiler som stöds innan uppladdning + Metadata kunde inte tas bort + Detta filformat stöder inte borttagning av metadata. Privat information som plats och enhetsinformation kan ingå. Ladda upp ändå? + Ladda upp ändå + Kunde inte ta bort privat metadata från media. Uppladdning avbruten. + Uppladdning avbruten Redigera utkast Logga in med QR-kod Rutt diff --git a/amethyst/src/main/res/values-zh-rCN/strings.xml b/amethyst/src/main/res/values-zh-rCN/strings.xml index 95b1bad1c2..5a14e0eebe 100644 --- a/amethyst/src/main/res/values-zh-rCN/strings.xml +++ b/amethyst/src/main/res/values-zh-rCN/strings.xml @@ -638,6 +638,10 @@ %1$s聪 来自 %1$s 为 %1$s + 回复 + 标记为已读 + 新信息 + 新打闪 通知: 加入对话 用户或群组 ID @@ -1034,6 +1038,12 @@ 无法准备头部信息:%1$s 压缩已取消 压缩返回的文件失败 + 加密文件 + 上传前为了隐私加密文件。有些服务器可能不接受免费账户的加密文件。 + 加密上传失败 + 许多服务器不接受免费账户的加密文件。您可以不加密重试。 + 不加密重试 + 警告:没有加密,任何有文件链接的人都可以看到内容。 媒体质量 选择「低质量」来将你的媒体文件压缩到较小体积,或者选择「高质量」来将你的媒体文件压缩到较大体积。 低质量 @@ -1042,6 +1052,13 @@ 未压缩 使用 H.265/HEVC 编解码器 文件较小而质量更佳,但不是所有设备都支持 H.265 播放。 + 删除私密元数据 + 尝试在上传之前从支持的媒体文件中删除私密元数据 + 无法删除元数据 + 此文件格式不支持删除元数据。可以包含位置和设备信息等私人信息。仍然要上传吗? + 仍然上传 + 从媒体删除私密元数据失败。上传取消。 + 上传已取消 编辑草稿 使用二维码登录 路径 @@ -1076,6 +1093,9 @@ 已收到 已发送 刷新 + 全部 + 打闪 + 非打闪 安全滤镜 导入关注 新帖子 @@ -1143,6 +1163,7 @@ 示例:\n - auth.nostr1.com (免费)\n - inbox.nostr.wine (付费)\n - revisy.0xchat.com (免费) 设置 1 ~ 3 个私人收件箱中继。需要确保这些收件箱中继能够接受来自任何人的任何私信消息,但只允许您读取这些消息。 立即设置 + 未找到私信收件箱中继。配置中继列表前无法传送消息。 搜索中继 设置你的搜索中继 通过创建专用于关键词和标签检索的中继列表能够改善搜索结果。 @@ -1558,9 +1579,51 @@ 全选 %1$d%% 运行时间 Namecoin 设置 + 中继同步 + 中继同步 + 在所有已知的中继重新发布您的事件,以保持您的发件箱、收件箱和私信中继是最新的。 需要 Wi-Fi - 这可能使用大量数据。 + 打开中继同步… + 这是做什么 + 此工具扫描您的应用已看到的每一个中继并将您的事件重新分发到正确的目的地: + 下载您编写的所有事件并发送到您的发件箱中继中。 + 下载所有提到您的事件并发送到您的收件箱中继中。 + 下载发送给您的所有私信并发送到您的私信中继。 + ⚠ 您似乎在按流量计费或移动网络连接上。此操作可能传输大量数据。启动前请连接到 Wi-Fi。 + 使用移动数据? + 开始同步 + 仍要启动(移动数据) + 暂停 + 恢复 + 重来 + 取消 + 中继: %1$d / %2$d + 已重新分发事件:%1$d 个新事件,共 %2$d 个已发送事件和 %3$d 个已接收事件 + 同步已暂停 + 完成了 %2$d 个中继中的 %1$d 个中继 — 迄今为止重新分发了 %3$d 个事件。轻按“恢复“继续。 + 同步完成 + 转发了 %1$d 个事件到目标中继,共接收到 %2$d 个事件。 + %1$d 个事件被目标中继接受为新事件。 + 在 %1$d 秒内完成。 + 同步错误 + 发送到 + 发件箱 + 收件箱 + 私信 + 当前正在检查 (%1$d 个中继) + 已完成 (%1$d 个中继) + 已发送 %1$s + 已收到 %1$s + 新增 %1$s + 没有事件 比特币资源管理器 (OTS) 事件 私信 个人资料 中继设置 + 上次看见在 %1$s 秒前 + <%1$s + 连接中 + 下载中 + 错误 + 已完成 diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index a935341290..a506604f92 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1545,7 +1545,11 @@ My Lists/Sets My Lists Users - Select a list to filter the feed + Select an option to filter the feed + Feeds + Hashtags + Communities + Lists Log off on device lock Private Message