From 150177f5a0220bf6e84d23e6f839186cc989f8b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 21:13:01 +0000 Subject: [PATCH] fix(strings): move the cordn and backup strings to commonsUI, and write the rule down The 2026-09-22 move left Android res/ with only the ~190 strings a platform API reads synchronously. The cordn UI and the backup-conflict review then added 447 keys back to it. All of them move with tools/strings-migrate (1,131 elements across the locales that translate them), and the 18 files that read them now use Res.string/Res.plurals. The parts that were not a rename: - stringRes(context, ...) in the suspend attachment/voice senders becomes loadStringRes; adminFailureText becomes suspend (its only caller is already inside scope.launch). - The backup review held labels as @StringRes Int (DiffGroup.label, the ReviewRow *Res fields, eventTypeName/Explainer, the tile/hero/lane params). They are StringResource / PluralStringResource now. - rememberPresentation resolved labels inside remember {} through LocalResources. Compose resources have no blocking accessor, so the 16 labels presentationOf writes into item text are resolved in composition into a map, and that map is the remember key. - androidx pluralStringResource -> the compose-resources one. The rule is now in .claude/CLAUDE.md ("Strings"), commonsUI/ARCHITECTURE.md and the android-expert and find-missing-translations skills: new strings go in commonsUI composeResources, Android-only screens included. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UjQQN9CgWHVtNCSWqnKoqY --- .claude/CLAUDE.md | 42 ++ .claude/skills/android-expert/SKILL.md | 21 +- .../skills/find-missing-translations/SKILL.md | 7 + .../loggedIn/backups/BackupConflictCards.kt | 131 ++-- .../backups/BackupConflictEventViews.kt | 197 +++--- .../backups/BackupConflictListViews.kt | 156 +++-- .../backups/BackupConflictPresentation.kt | 328 ++++++---- .../backups/BackupConflictReviewScreen.kt | 86 ++- .../chats/cordnGroup/CordnComposer.kt | 5 +- .../cordnGroup/CordnCreateGroupScreen.kt | 149 +++-- .../cordnGroup/CordnCreateMembersScreen.kt | 58 +- .../chats/cordnGroup/CordnGroupChatScreen.kt | 78 ++- .../chats/cordnGroup/CordnGroupInfoScreen.kt | 161 +++-- .../chats/cordnGroup/CordnGroupListScreen.kt | 26 +- .../cordnGroup/CordnInvitationsScreen.kt | 51 +- .../chats/cordnGroup/CordnMessageRow.kt | 61 +- .../settings/cordn/CordnBackupScreen.kt | 45 +- .../settings/cordn/CordnCoordinatorsScreen.kt | 114 ++-- .../settings/cordn/CordnKeyPackagesScreen.kt | 50 +- .../settings/cordn/CordnLinkScreen.kt | 16 +- .../settings/cordn/CordnMigrateScreen.kt | 7 +- .../src/main/res/values-hi-rIN/strings.xml | 318 ---------- .../src/main/res/values-hu-rHU/strings.xml | 318 ---------- .../src/main/res/values-pl-rPL/strings.xml | 378 ------------ amethyst/src/main/res/values/strings.xml | 570 ------------------ commonsUI/ARCHITECTURE.md | 5 +- .../values-hi-rIN/strings.xml | 318 ++++++++++ .../values-hu-rHU/strings.xml | 318 ++++++++++ .../values-pl-rPL/strings.xml | 378 ++++++++++++ .../composeResources/values/strings.xml | 570 ++++++++++++++++++ 30 files changed, 2777 insertions(+), 2185 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 6038af56e7..886a0dfe3e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -285,6 +285,48 @@ Quartz uses expect/actual for platform-specific implementations (e.g. crypto backed by `secp256k1-kmp-jni-android` on Android and `secp256k1-kmp-jni-jvm` on JVM). See `/kotlin-multiplatform` for the expect/actual and source-set patterns. +## Strings + +**MANDATORY: every new user-visible string goes in `commonsUI` Compose resources**, +`commonsUI/src/commonMain/composeResources/values/strings.xml` — **never** in +`amethyst/src/main/res/values/strings.xml`, even for an Android-only screen. A +screen cannot move to `commonsUI` (or be rendered by Desktop) while its labels +live in Android `res/`, and every key added there has to be migrated back out +later. (The 2026-09-22 move emptied Android `res/` down to ~190 keys; the next two +features put 447 back.) + +- Reference it as `Res.string.my_key` / `Res.plurals.my_key`, importing + `com.vitorpamplona.amethyst.commons.resources.Res` **and** the per-key accessor + `com.vitorpamplona.amethyst.commons.resources.my_key` (Compose resources generates + each key as an extension property). +- Read it with `stringRes(Res.string.x)` / `pluralStringRes(Res.plurals.x, n, …)` + in composition — the app-side `ui.stringRes` overloads delegate to the + commonsUI bridge (`commons/ui/StringRes.kt`), so one import serves both kinds. +- Outside composition there is **no blocking accessor**: use `loadStringRes(…)` + from a coroutine (or make the function `suspend`), or resolve the label in + composition and pass it down as a `String`. A `runBlocking` bridge is ruled out. +- Hold a string reference as `StringResource` / `PluralStringResource`, not a + `@StringRes Int`. +- Format arguments must be positional (`%1$s`, `%2$d`) — compose-resources does + not format bare `%s`/`%d`. +- Escaping is not Android's: write `'` and `"` bare (no `\'`), and never wrap a + value in quotes to keep its whitespace. `.claude/hooks/compose_escaping_check.py` + checks this before a push. + +The **only** strings that stay in Android `res/` are the ones a platform API +reads synchronously under a deadline, where a `suspend` read cannot run: a +foreground service's `startForeground` notification, a notification channel +created during service startup, PiP `RemoteAction`s, napplet capability labels +read in the sandbox process, and `AndroidManifest.xml` / `res/xml` references. +Most of those are *copies* of a key that also lives in `commonsUI`. See +`commons/plans/2026-09-22-strings-to-compose-resources.md` ("the +synchronous-platform tier"). If you think a new string belongs in that tier, +say why in the PR. + +To move keys that already landed in Android `res/`, use +`tools/strings-migrate/migrate.py `. It moves every locale byte-for-byte, +so Crowdin sees a pure move. Then repoint `R.string.x` → `Res.string.x`. + ## Icons The Material Symbols font bundled at diff --git a/.claude/skills/android-expert/SKILL.md b/.claude/skills/android-expert/SKILL.md index e2483221d0..19b79ec6a1 100644 --- a/.claude/skills/android-expert/SKILL.md +++ b/.claude/skills/android-expert/SKILL.md @@ -875,18 +875,33 @@ fun ProfileScreen( ### 3. Resource Access +**Strings do not go in `amethyst/src/main/res`.** New user-visible strings live in +`commonsUI/src/commonMain/composeResources/values/strings.xml` and are read as +`Res.string.x`, even on an Android-only screen. See "Strings" in `.claude/CLAUDE.md` +for the rule and the small platform tier that is the only exception. + ```kotlin +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.button_clicked +import com.vitorpamplona.amethyst.commons.resources.button_label +import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.ui.stringRes + @Composable fun LocalizedButton() { val context = LocalContext.current + val scope = rememberCoroutineScope() Button( onClick = { - val message = context.getString(R.string.button_clicked) - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + // No blocking accessor outside composition: read it from a coroutine, + // or resolve it in composition and capture the String. + scope.launch { + Toast.makeText(context, loadStringRes(Res.string.button_clicked), Toast.LENGTH_SHORT).show() + } } ) { - Text(stringResource(R.string.button_label)) + Text(stringRes(Res.string.button_label)) } } ``` diff --git a/.claude/skills/find-missing-translations/SKILL.md b/.claude/skills/find-missing-translations/SKILL.md index b41533cf3e..7547c5c46d 100644 --- a/.claude/skills/find-missing-translations/SKILL.md +++ b/.claude/skills/find-missing-translations/SKILL.md @@ -26,6 +26,13 @@ There are two separate `strings.xml` trees, each with its own default `values/` | **amethyst** (Android app) | `amethyst/src/main/res/values/strings.xml` | `amethyst/src/main/res/values-/strings.xml` | | **commonsUI** (KMP Compose resources, shared by Android + Desktop) | `commonsUI/src/commonMain/composeResources/values/strings.xml` | `commonsUI/src/commonMain/composeResources/values-/strings.xml` | +**New keys go in the `commonsUI` tree, always.** The amethyst tree is frozen at +the small synchronous-platform tier (foreground-service and notification-channel +text, PiP actions, napplet capability labels, manifest references). If you find +a new feature key in it, it belongs in `commonsUI`. Move it with +`tools/strings-migrate/migrate.py` rather than translating it where it is. See +"Strings" in `.claude/CLAUDE.md`. + The `commonsUI` tree appeared when shared event-renderer composables were extracted out of `amethyst/` into `commons/` — now `commonsUI/` since the UI split (Compose Multiplatform `stringResource`). It is **not** a copy of the amethyst tree — the vast majority of its keys are commons-only; only a small handful overlap. Every diff/count/translate command below works on either tree by swapping the base path — **run the whole technique once per tree** and report them separately (each maps to its own Crowdin file, so the counts should reconcile against two different Crowdin UI numbers). **Locale-qualifier caveat:** `commons` uses the same region-qualified locale dirs as amethyst for our four targets (`values-cs`, `values-de-rDE`, `values-sv-rSE`, `values-pt-rBR`), but the *full* set of locale dirs differs between trees. Enumerate `values-*` under each tree's own base rather than assuming they match. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt index bb977a4184..769c911ae4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt @@ -43,17 +43,57 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict import com.vitorpamplona.amethyst.commons.model.navigation.Route +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.backup_card_accept +import com.vitorpamplona.amethyst.commons.resources.backup_card_and_added +import com.vitorpamplona.amethyst.commons.resources.backup_card_and_blocked +import com.vitorpamplona.amethyst.commons.resources.backup_card_and_followed +import com.vitorpamplona.amethyst.commons.resources.backup_card_and_joined +import com.vitorpamplona.amethyst.commons.resources.backup_card_and_muted +import com.vitorpamplona.amethyst.commons.resources.backup_card_app_settings +import com.vitorpamplona.amethyst.commons.resources.backup_card_concord_emptied +import com.vitorpamplona.amethyst.commons.resources.backup_card_concord_rewritten +import com.vitorpamplona.amethyst.commons.resources.backup_card_dm_relays_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_feeds_removed +import com.vitorpamplona.amethyst.commons.resources.backup_card_follows_dropped +import com.vitorpamplona.amethyst.commons.resources.backup_card_hashtags_unfollowed +import com.vitorpamplona.amethyst.commons.resources.backup_card_indexer_relays_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_key_package_relays_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_chats +import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_communities +import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_groups +import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_rooms +import com.vitorpamplona.amethyst.commons.resources.backup_card_mutes_visible +import com.vitorpamplona.amethyst.commons.resources.backup_card_nip65_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_key_removed +import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_key_replaced +import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_mints_removed +import com.vitorpamplona.amethyst.commons.resources.backup_card_offers_removed +import com.vitorpamplona.amethyst.commons.resources.backup_card_payment_targets_removed +import com.vitorpamplona.amethyst.commons.resources.backup_card_places_unfollowed +import com.vitorpamplona.amethyst.commons.resources.backup_card_private_outbox_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_profile_lost_fields +import com.vitorpamplona.amethyst.commons.resources.backup_card_relay_feeds_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_search_relays_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_trust_services_changed +import com.vitorpamplona.amethyst.commons.resources.backup_card_trusted_lost +import com.vitorpamplona.amethyst.commons.resources.backup_card_unblocked +import com.vitorpamplona.amethyst.commons.resources.backup_card_wallet_emptied +import com.vitorpamplona.amethyst.commons.resources.backup_card_wallet_rewritten +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_added +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_changed +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_removed +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_title +import com.vitorpamplona.amethyst.commons.resources.backup_review_changed_by_other_app import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot @@ -79,6 +119,9 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataDiff import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.pluralStringResource /** * Cards at the top of Home's feed, one per open [ReplaceableBackupConflict]: the user @@ -125,34 +168,34 @@ private fun headlineOf( when (diff) { // Counted, not "shrank": a version that drops 3 follows and adds 100 still raises // a conflict, and calling that a shrink contradicts the card's own bar. - is ContactListDiff -> plural(R.plurals.backup_card_follows_dropped, diff.follows.removed.size) - is MuteListDiff -> plural(R.plurals.backup_card_mutes_visible, diff.publicMutes.removed.size) - is MetadataDiff -> plural(R.plurals.backup_card_profile_lost_fields, counts.removed) - is AdvertisedRelayListDiff -> plural(R.plurals.backup_card_nip65_lost, diff.relays.removed.size) + is ContactListDiff -> plural(Res.plurals.backup_card_follows_dropped, diff.follows.removed.size) + is MuteListDiff -> plural(Res.plurals.backup_card_mutes_visible, diff.publicMutes.removed.size) + is MetadataDiff -> plural(Res.plurals.backup_card_profile_lost_fields, counts.removed) + is AdvertisedRelayListDiff -> plural(Res.plurals.backup_card_nip65_lost, diff.relays.removed.size) is RelayListDiff -> relayListHeadline(conflict.eventType, diff.relays.removed.size) - is ChannelListDiff -> plural(R.plurals.backup_card_leave_chats, diff.channels.removed.size) - is CommunityListDiff -> plural(R.plurals.backup_card_leave_communities, diff.communities.removed.size) - is EphemeralChatListDiff -> plural(R.plurals.backup_card_leave_rooms, diff.rooms.removed.size) - is SimpleGroupListDiff -> plural(R.plurals.backup_card_leave_groups, diff.groups.removed.size) - is FavoriteAlgoFeedsListDiff -> plural(R.plurals.backup_card_feeds_removed, diff.feeds.removed.size) - is HashtagListDiff -> plural(R.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size) - is GeohashListDiff -> plural(R.plurals.backup_card_places_unfollowed, diff.geohashes.removed.size) - is TrustProviderListDiff -> plural(R.plurals.backup_card_trust_services_changed, diff.providers.removed.size + diff.providers.changed.size) + is ChannelListDiff -> plural(Res.plurals.backup_card_leave_chats, diff.channels.removed.size) + is CommunityListDiff -> plural(Res.plurals.backup_card_leave_communities, diff.communities.removed.size) + is EphemeralChatListDiff -> plural(Res.plurals.backup_card_leave_rooms, diff.rooms.removed.size) + is SimpleGroupListDiff -> plural(Res.plurals.backup_card_leave_groups, diff.groups.removed.size) + is FavoriteAlgoFeedsListDiff -> plural(Res.plurals.backup_card_feeds_removed, diff.feeds.removed.size) + is HashtagListDiff -> plural(Res.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size) + is GeohashListDiff -> plural(Res.plurals.backup_card_places_unfollowed, diff.geohashes.removed.size) + is TrustProviderListDiff -> plural(Res.plurals.backup_card_trust_services_changed, diff.providers.removed.size + diff.providers.changed.size) is NutzapInfoDiff -> when { - diff.p2pkPubkey?.after == null && diff.p2pkPubkey != null -> stringRes(R.string.backup_card_nutzap_key_removed) - diff.p2pkPubkey != null -> stringRes(R.string.backup_card_nutzap_key_replaced) - else -> plural(R.plurals.backup_card_nutzap_mints_removed, diff.mints.removed.size) + diff.p2pkPubkey?.after == null && diff.p2pkPubkey != null -> stringRes(Res.string.backup_card_nutzap_key_removed) + diff.p2pkPubkey != null -> stringRes(Res.string.backup_card_nutzap_key_replaced) + else -> plural(Res.plurals.backup_card_nutzap_mints_removed, diff.mints.removed.size) } - is PaymentTargetsDiff -> plural(R.plurals.backup_card_payment_targets_removed, diff.targets.removed.size) - is Bolt12OfferListDiff -> plural(R.plurals.backup_card_offers_removed, diff.offers.removed.size) - is CashuWalletDiff -> encryptedHeadline(R.string.backup_card_wallet_emptied, R.string.backup_card_wallet_rewritten, diff.wallet) - is ConcordCommunityListDiff -> encryptedHeadline(R.string.backup_card_concord_emptied, R.string.backup_card_concord_rewritten, diff.communities) - is AppSpecificDataDiff -> stringRes(R.string.backup_card_app_settings) + is PaymentTargetsDiff -> plural(Res.plurals.backup_card_payment_targets_removed, diff.targets.removed.size) + is Bolt12OfferListDiff -> plural(Res.plurals.backup_card_offers_removed, diff.offers.removed.size) + is CashuWalletDiff -> encryptedHeadline(Res.string.backup_card_wallet_emptied, Res.string.backup_card_wallet_rewritten, diff.wallet) + is ConcordCommunityListDiff -> encryptedHeadline(Res.string.backup_card_concord_emptied, Res.string.backup_card_concord_rewritten, diff.communities) + is AppSpecificDataDiff -> stringRes(Res.string.backup_card_app_settings) else -> null } // A headline counting removed items reads wrong when only encrypted items were lost. - if (specific.isNullOrEmpty()) return stringRes(R.string.backup_conflict_title, stringRes(eventTypeName(conflict.eventType))) + if (specific.isNullOrEmpty()) return stringRes(Res.string.backup_conflict_title, stringRes(eventTypeName(conflict.eventType))) // Headlines lead with the loss; what the other app added to the same list is said too, // so a list that dropped 3 and gained 40 doesn't read as if it only shrank. The wording // follows the headline's: following people, muting, joining chats, blocking relays. @@ -160,11 +203,11 @@ private fun headlineOf( if (added <= 0) return specific val tail = when { - diff is ContactListDiff -> R.plurals.backup_card_and_followed - diff is MuteListDiff -> R.plurals.backup_card_and_muted - diff is ChannelListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> R.plurals.backup_card_and_joined - conflict.eventType == BackupEventType.BLOCKED_RELAYS -> R.plurals.backup_card_and_blocked - else -> R.plurals.backup_card_and_added + diff is ContactListDiff -> Res.plurals.backup_card_and_followed + diff is MuteListDiff -> Res.plurals.backup_card_and_muted + diff is ChannelListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> Res.plurals.backup_card_and_joined + conflict.eventType == BackupEventType.BLOCKED_RELAYS -> Res.plurals.backup_card_and_blocked + else -> Res.plurals.backup_card_and_added } return pluralStringResource(tail, added, specific, added) } @@ -208,7 +251,7 @@ private fun relayListHasHeadline(type: BackupEventType): Boolean = /** The count's plural, or "" when nothing of that kind was removed. */ @Composable private fun plural( - id: Int, + id: PluralStringResource, count: Int, ): String = if (count > 0) pluralStringResource(id, count, count) else "" @@ -218,21 +261,21 @@ private fun relayListHeadline( removed: Int, ): String = when (type) { - BackupEventType.DM_RELAYS -> plural(R.plurals.backup_card_dm_relays_lost, removed) - BackupEventType.KEY_PACKAGE_RELAYS -> plural(R.plurals.backup_card_key_package_relays_lost, removed) - BackupEventType.SEARCH_RELAYS -> plural(R.plurals.backup_card_search_relays_lost, removed) - BackupEventType.INDEXER_RELAYS -> plural(R.plurals.backup_card_indexer_relays_lost, removed) - BackupEventType.RELAY_FEEDS -> plural(R.plurals.backup_card_relay_feeds_lost, removed) - BackupEventType.PRIVATE_OUTBOX_RELAYS -> plural(R.plurals.backup_card_private_outbox_lost, removed) - BackupEventType.TRUSTED_RELAYS -> plural(R.plurals.backup_card_trusted_lost, removed) - BackupEventType.BLOCKED_RELAYS -> plural(R.plurals.backup_card_unblocked, removed) + BackupEventType.DM_RELAYS -> plural(Res.plurals.backup_card_dm_relays_lost, removed) + BackupEventType.KEY_PACKAGE_RELAYS -> plural(Res.plurals.backup_card_key_package_relays_lost, removed) + BackupEventType.SEARCH_RELAYS -> plural(Res.plurals.backup_card_search_relays_lost, removed) + BackupEventType.INDEXER_RELAYS -> plural(Res.plurals.backup_card_indexer_relays_lost, removed) + BackupEventType.RELAY_FEEDS -> plural(Res.plurals.backup_card_relay_feeds_lost, removed) + BackupEventType.PRIVATE_OUTBOX_RELAYS -> plural(Res.plurals.backup_card_private_outbox_lost, removed) + BackupEventType.TRUSTED_RELAYS -> plural(Res.plurals.backup_card_trusted_lost, removed) + BackupEventType.BLOCKED_RELAYS -> plural(Res.plurals.backup_card_unblocked, removed) else -> "" } @Composable private fun encryptedHeadline( - emptied: Int, - rewritten: Int, + emptied: StringResource, + rewritten: StringResource, change: ContentChange, ): String = if (change == ContentChange.CLEARED) stringRes(emptied) else stringRes(rewritten) @@ -262,7 +305,7 @@ private fun LeadConflictCard( Column(Modifier.weight(1f)) { Text(headlineOf(conflict, counts), style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, maxLines = 3, overflow = TextOverflow.Ellipsis) Text( - stringRes(R.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)), + stringRes(Res.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.placeholderText, ) @@ -296,9 +339,9 @@ private fun LeadConflictCard( private fun CountChips(counts: ConflictCounts) { val tones = conflictTones() Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - if (counts.removed > 0) StatusTag(stringRes(R.string.backup_conflict_count_removed, counts.removed.toString()), tones.removed) - if (counts.added > 0) StatusTag(stringRes(R.string.backup_conflict_count_added, counts.added.toString()), tones.added) - if (counts.changed > 0) StatusTag(stringRes(R.string.backup_conflict_count_changed, counts.changed.toString()), tones.changed) + if (counts.removed > 0) StatusTag(stringRes(Res.string.backup_conflict_count_removed, counts.removed.toString()), tones.removed) + if (counts.added > 0) StatusTag(stringRes(Res.string.backup_conflict_count_added, counts.added.toString()), tones.added) + if (counts.changed > 0) StatusTag(stringRes(Res.string.backup_conflict_count_changed, counts.changed.toString()), tones.changed) } } @@ -342,6 +385,6 @@ private fun AcceptButton(onAccept: () -> Unit) { modifier = Modifier.height(36.dp), contentPadding = PaddingValues(horizontal = 14.dp), ) { - Text(stringRes(R.string.backup_card_accept), fontWeight = FontWeight.SemiBold) + Text(stringRes(Res.string.backup_card_accept), fontWeight = FontWeight.SemiBold) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt index 419bd3b2a2..1916f09286 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt @@ -62,7 +62,6 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict @@ -70,6 +69,67 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.routeFor import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.backup_entry_hashtag +import com.vitorpamplona.amethyst.commons.resources.backup_entry_mint +import com.vitorpamplona.amethyst.commons.resources.backup_entry_nutzap_key +import com.vitorpamplona.amethyst.commons.resources.backup_entry_person +import com.vitorpamplona.amethyst.commons.resources.backup_entry_profile_field +import com.vitorpamplona.amethyst.commons.resources.backup_entry_thread +import com.vitorpamplona.amethyst.commons.resources.backup_entry_word +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_about +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_banner +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_birthday +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_bot +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_clink_offer +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_display_name +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud06 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud16 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_name +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_nip05 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_picture +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_pronouns +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_website +import com.vitorpamplona.amethyst.commons.resources.backup_review_demoted_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_count +import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_gone +import com.vitorpamplona.amethyst.commons.resources.backup_review_inbox +import com.vitorpamplona.amethyst.commons.resources.backup_review_inbox_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_joined +import com.vitorpamplona.amethyst.commons.resources.backup_review_kept_count +import com.vitorpamplona.amethyst.commons.resources.backup_review_key_removed +import com.vitorpamplona.amethyst.commons.resources.backup_review_key_replaced +import com.vitorpamplona.amethyst.commons.resources.backup_review_key_warning +import com.vitorpamplona.amethyst.commons.resources.backup_review_left +import com.vitorpamplona.amethyst.commons.resources.backup_review_left_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_names_and_more +import com.vitorpamplona.amethyst.commons.resources.backup_review_new +import com.vitorpamplona.amethyst.commons.resources.backup_review_new_count +import com.vitorpamplona.amethyst.commons.resources.backup_review_new_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_newly_muted +import com.vitorpamplona.amethyst.commons.resources.backup_review_not_set +import com.vitorpamplona.amethyst.commons.resources.backup_review_nothing_here +import com.vitorpamplona.amethyst.commons.resources.backup_review_outbox +import com.vitorpamplona.amethyst.commons.resources.backup_review_outbox_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_added_body +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_added_title +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_changed_body +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_changed_title +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_wiped_body +import com.vitorpamplona.amethyst.commons.resources.backup_review_private_wiped_title +import com.vitorpamplona.amethyst.commons.resources.backup_review_renamed +import com.vitorpamplona.amethyst.commons.resources.backup_review_renamed_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_saved +import com.vitorpamplona.amethyst.commons.resources.backup_review_search_people +import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_dropped +import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_edited +import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_new +import com.vitorpamplona.amethyst.commons.resources.backup_review_units_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_unmuted +import com.vitorpamplona.amethyst.commons.resources.backup_review_unmuted_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_was_named +import com.vitorpamplona.amethyst.commons.resources.backup_review_words_and_hashtags import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings @@ -111,6 +171,7 @@ import com.vitorpamplona.quartz.nip61Nutzaps.info.tags.NutzapMintTag import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListDiff import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType +import org.jetbrains.compose.resources.StringResource private const val GRID_COLUMNS = 4 private const val PEOPLE_PREVIEW = 6 @@ -168,7 +229,7 @@ private fun LazyListScope.followListItems( item(key = "follow-hero", contentType = "hero") { val tones = conflictTones() Column(Pad.padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - HeroCounts(stringRes(R.string.backup_review_saved), saved, stringRes(R.string.backup_review_new), new) + HeroCounts(stringRes(Res.string.backup_review_saved), saved, stringRes(Res.string.backup_review_new), new) SplitBar( listOf( BarSegment(saved - dropped, tones.kept), @@ -177,9 +238,9 @@ private fun LazyListScope.followListItems( ), ) Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) { - LegendItem(tones.kept, stringRes(R.string.backup_review_kept_count, (saved - dropped).toString())) - LegendItem(tones.removed, stringRes(R.string.backup_review_dropped_count, dropped.toString())) - LegendItem(tones.added, stringRes(R.string.backup_review_new_count, gained.toString())) + LegendItem(tones.kept, stringRes(Res.string.backup_review_kept_count, (saved - dropped).toString())) + LegendItem(tones.removed, stringRes(Res.string.backup_review_dropped_count, dropped.toString())) + LegendItem(tones.added, stringRes(Res.string.backup_review_new_count, gained.toString())) } } } @@ -188,9 +249,9 @@ private fun LazyListScope.followListItems( ReviewTabs( labels = listOf( - stringRes(R.string.backup_review_tab_dropped, dropped.toString()), - stringRes(R.string.backup_review_tab_new, gained.toString()), - stringRes(R.string.backup_review_tab_edited, edited.toString()), + stringRes(Res.string.backup_review_tab_dropped, dropped.toString()), + stringRes(Res.string.backup_review_tab_new, gained.toString()), + stringRes(Res.string.backup_review_tab_edited, edited.toString()), ), selected = ui.tab, onSelect = { @@ -223,7 +284,7 @@ private fun LazyListScope.followListItems( if (filtered.isEmpty()) { item(key = "follow-empty", contentType = "note") { - DetailText(stringRes(R.string.backup_review_nothing_here), Pad.padding(vertical = 24.dp)) + DetailText(stringRes(Res.string.backup_review_nothing_here), Pad.padding(vertical = 24.dp)) } } } @@ -359,7 +420,7 @@ private fun PeopleSearch( singleLine = true, shape = RoundedCornerShape(14.dp), leadingIcon = { Icon(symbol = MaterialSymbols.Search, contentDescription = null) }, - placeholder = { Text(stringRes(R.string.backup_review_search_people, count.toString())) }, + placeholder = { Text(stringRes(Res.string.backup_review_search_people, count.toString())) }, ) } @@ -388,15 +449,15 @@ private fun LazyListScope.muteListItems( Icon(symbol = MaterialSymbols.Shield, contentDescription = null, tint = tones.removed, modifier = Modifier.size(44.dp)) Column { Text( - stringRes(R.string.backup_review_unmuted, removed.size.toString()), + stringRes(Res.string.backup_review_unmuted, removed.size.toString()), fontSize = 30.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold, color = tones.removed, ) - Text(stringRes(R.string.backup_review_unmuted_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) + Text(stringRes(Res.string.backup_review_unmuted_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) if (added.isNotEmpty()) { - Text(stringRes(R.string.backup_review_newly_muted, added.size.toString()), style = MaterialTheme.typography.bodySmall, color = tones.added) + Text(stringRes(Res.string.backup_review_newly_muted, added.size.toString()), style = MaterialTheme.typography.bodySmall, color = tones.added) } } } @@ -406,17 +467,17 @@ private fun LazyListScope.muteListItems( item(key = "mute-tiles", contentType = "tiles") { val tones = conflictTones() Row(Pad.padding(top = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - StatTile("−${people.size}", stringRes(R.string.backup_entry_person), tones.removed, Modifier.weight(1f)) - StatTile("−${words.size}", stringRes(R.string.backup_entry_word), tones.removed, Modifier.weight(1f)) - StatTile("−${hashtags.size}", stringRes(R.string.backup_entry_hashtag), tones.removed, Modifier.weight(1f)) - StatTile("−${threads.size}", stringRes(R.string.backup_entry_thread), tones.removed, Modifier.weight(1f)) + StatTile("−${people.size}", stringRes(Res.string.backup_entry_person), tones.removed, Modifier.weight(1f)) + StatTile("−${words.size}", stringRes(Res.string.backup_entry_word), tones.removed, Modifier.weight(1f)) + StatTile("−${hashtags.size}", stringRes(Res.string.backup_entry_hashtag), tones.removed, Modifier.weight(1f)) + StatTile("−${threads.size}", stringRes(Res.string.backup_entry_thread), tones.removed, Modifier.weight(1f)) } } if (people.isNotEmpty()) { item(key = "mute-people", contentType = "facepile") { Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - SectionCaption(stringRes(R.string.backup_entry_person)) + SectionCaption(stringRes(Res.string.backup_entry_person)) FacePile(people, accountViewModel) { ui.showAllPeople = !ui.showAllPeople } } } @@ -427,7 +488,7 @@ private fun LazyListScope.muteListItems( item(key = "mute-words", contentType = "pills") { val tones = conflictTones() Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - SectionCaption(stringRes(R.string.backup_review_words_and_hashtags)) + SectionCaption(stringRes(Res.string.backup_review_words_and_hashtags)) FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { (words.map { "\"$it\"" } + hashtags).forEach { StruckPill(it, tones.removed) } } @@ -436,7 +497,7 @@ private fun LazyListScope.muteListItems( } if (threads.isNotEmpty()) { - item(key = "mute-threads-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_entry_thread), Pad.padding(top = 18.dp)) } + item(key = "mute-threads-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_entry_thread), Pad.padding(top = 18.dp)) } items(threads, key = { "mute-thread-$it" }, contentType = { "note" }) { id -> LoadNote(id) { note -> if (note != null) { @@ -463,7 +524,7 @@ private fun FacePile( val names = people.take(2).map { observedName(it, accountViewModel) } val summary = if (people.size > 2) { - stringRes(R.string.backup_review_names_and_more, names.joinToString(", "), (people.size - 2).toString()) + stringRes(Res.string.backup_review_names_and_more, names.joinToString(", "), (people.size - 2).toString()) } else { names.joinToString(", ") } @@ -564,9 +625,9 @@ internal fun PrivateItemsCard( val tones = conflictTones() val (title, body) = when (change) { - ContentChange.CLEARED -> R.string.backup_review_private_wiped_title to R.string.backup_review_private_wiped_body - ContentChange.ADDED -> R.string.backup_review_private_added_title to R.string.backup_review_private_added_body - else -> R.string.backup_review_private_changed_title to R.string.backup_review_private_changed_body + ContentChange.CLEARED -> Res.string.backup_review_private_wiped_title to Res.string.backup_review_private_wiped_body + ContentChange.ADDED -> Res.string.backup_review_private_added_title to Res.string.backup_review_private_added_body + else -> Res.string.backup_review_private_changed_title to Res.string.backup_review_private_changed_body } Row( modifier @@ -592,28 +653,28 @@ internal fun PrivateItemsCard( // --------------------------------------------------------------------------------------- private class ProfileField( - val labelRes: Int, + val labelRes: StringResource, val change: ValueChange, val isImage: Boolean = false, ) private fun profileFields(diff: MetadataDiff): List = listOfNotNull( - diff.picture?.let { ProfileField(R.string.backup_profile_field_picture, it, isImage = true) }, - diff.banner?.let { ProfileField(R.string.backup_profile_field_banner, it, isImage = true) }, - diff.name?.let { ProfileField(R.string.backup_profile_field_name, it) }, - diff.displayName?.let { ProfileField(R.string.backup_profile_field_display_name, it) }, - diff.about?.let { ProfileField(R.string.backup_profile_field_about, it) }, - diff.nip05?.let { ProfileField(R.string.backup_profile_field_nip05, it) }, - diff.website?.let { ProfileField(R.string.backup_profile_field_website, it) }, - diff.lud16?.let { ProfileField(R.string.backup_profile_field_lud16, it) }, - diff.lud06?.let { ProfileField(R.string.backup_profile_field_lud06, it) }, - diff.clinkOffer?.let { ProfileField(R.string.backup_profile_field_clink_offer, it) }, - diff.pronouns?.let { ProfileField(R.string.backup_profile_field_pronouns, it) }, - diff.bot?.let { ProfileField(R.string.backup_profile_field_bot, ValueChange(it.before?.toString(), it.after?.toString())) }, + diff.picture?.let { ProfileField(Res.string.backup_profile_field_picture, it, isImage = true) }, + diff.banner?.let { ProfileField(Res.string.backup_profile_field_banner, it, isImage = true) }, + diff.name?.let { ProfileField(Res.string.backup_profile_field_name, it) }, + diff.displayName?.let { ProfileField(Res.string.backup_profile_field_display_name, it) }, + diff.about?.let { ProfileField(Res.string.backup_profile_field_about, it) }, + diff.nip05?.let { ProfileField(Res.string.backup_profile_field_nip05, it) }, + diff.website?.let { ProfileField(Res.string.backup_profile_field_website, it) }, + diff.lud16?.let { ProfileField(Res.string.backup_profile_field_lud16, it) }, + diff.lud06?.let { ProfileField(Res.string.backup_profile_field_lud06, it) }, + diff.clinkOffer?.let { ProfileField(Res.string.backup_profile_field_clink_offer, it) }, + diff.pronouns?.let { ProfileField(Res.string.backup_profile_field_pronouns, it) }, + diff.bot?.let { ProfileField(Res.string.backup_profile_field_bot, ValueChange(it.before?.toString(), it.after?.toString())) }, diff.birthday?.let { ProfileField( - R.string.backup_profile_field_birthday, + Res.string.backup_profile_field_birthday, ValueChange( it.before?.let { b -> listOfNotNull(b.year, b.month, b.day).joinToString("-") }, it.after?.let { a -> listOfNotNull(a.year, a.month, a.day).joinToString("-") }, @@ -636,8 +697,8 @@ private fun LazyListScope.profileItems( item(key = "profile-cards", contentType = "profile-cards") { val tones = conflictTones() Row(Pad.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(10.dp)) { - MiniProfileCard(saved, pubKey, stringRes(R.string.backup_review_saved), MaterialTheme.colorScheme.outlineVariant, accountViewModel, Modifier.weight(1f)) - MiniProfileCard(incoming, pubKey, stringRes(R.string.backup_review_new), tones.removed.copy(alpha = 0.5f), accountViewModel, Modifier.weight(1f)) + MiniProfileCard(saved, pubKey, stringRes(Res.string.backup_review_saved), MaterialTheme.colorScheme.outlineVariant, accountViewModel, Modifier.weight(1f)) + MiniProfileCard(incoming, pubKey, stringRes(Res.string.backup_review_new), tones.removed.copy(alpha = 0.5f), accountViewModel, Modifier.weight(1f)) } } @@ -656,7 +717,7 @@ private fun LazyListScope.profileItems( } } // Linked identities and fields this app doesn't model use the generic sections. - genericDiffItems(buildRows(presentation, skipGroups = setOf(R.string.backup_entry_profile_field)), accountViewModel, nav) + genericDiffItems(buildRows(presentation, skipGroups = setOf(Res.string.backup_entry_profile_field)), accountViewModel, nav) } @Composable @@ -735,10 +796,10 @@ private fun FlowRowDiff( FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(6.dp), itemVerticalAlignment = Alignment.CenterVertically) { if (before != null) DiffChip(clip(before), tones.removed, struck = true) when { - after == null -> StatusTag(stringRes(R.string.backup_review_gone), tones.removed) + after == null -> StatusTag(stringRes(Res.string.backup_review_gone), tones.removed) before == null -> { DiffChip(clip(after), tones.added, struck = false) - StatusTag(stringRes(R.string.backup_review_new_tag), tones.added) + StatusTag(stringRes(Res.string.backup_review_new_tag), tones.added) } else -> { Text("→", color = MaterialTheme.colorScheme.placeholderText) @@ -779,7 +840,7 @@ private fun ImageThumb( contentAlignment = Alignment.Center, ) { if (url == null) { - Text(stringRes(R.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color) + Text(stringRes(Res.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color) } else { MyAsyncImage( imageUrl = url, @@ -843,14 +904,14 @@ private fun LazyListScope.nip65Items( val outbox = lane(saved, new, writes) val inbox = lane(saved, new, reads) - laneItems("outbox", stringRes = R.string.backup_review_outbox, subRes = R.string.backup_review_outbox_explainer, rows = outbox, nav = nav) - laneItems("inbox", stringRes = R.string.backup_review_inbox, subRes = R.string.backup_review_inbox_explainer, rows = inbox, nav = nav) + laneItems("outbox", stringRes = Res.string.backup_review_outbox, subRes = Res.string.backup_review_outbox_explainer, rows = outbox, nav = nav) + laneItems("inbox", stringRes = Res.string.backup_review_inbox, subRes = Res.string.backup_review_inbox_explainer, rows = inbox, nav = nav) } private fun LazyListScope.laneItems( key: String, - stringRes: Int, - subRes: Int, + stringRes: StringResource, + subRes: StringResource, rows: List, nav: INav, ) { @@ -873,9 +934,9 @@ private fun LaneRelayRow( val (color, tag) = when (row.state) { LaneState.KEPT -> tones.kept to null - LaneState.DROPPED -> tones.removed to R.string.backup_review_dropped_tag - LaneState.ADDED -> tones.added to R.string.backup_review_new_tag - LaneState.DEMOTED -> tones.changed to R.string.backup_review_demoted_tag + LaneState.DROPPED -> tones.removed to Res.string.backup_review_dropped_tag + LaneState.ADDED -> tones.added to Res.string.backup_review_new_tag + LaneState.DEMOTED -> tones.changed to Res.string.backup_review_demoted_tag } Row( Pad @@ -933,12 +994,12 @@ private fun LazyListScope.nutzapItems( ) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { Icon(symbol = MaterialSymbols.Key, contentDescription = null, tint = tones.changed) - Text(stringRes(if (key.after == null) R.string.backup_review_key_removed else R.string.backup_review_key_replaced), style = MaterialTheme.typography.titleSmall) + Text(stringRes(if (key.after == null) Res.string.backup_review_key_removed else Res.string.backup_review_key_replaced), style = MaterialTheme.typography.titleSmall) } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { - KeyColumn(key.before, stringRes(R.string.backup_review_saved), tones.kept) + KeyColumn(key.before, stringRes(Res.string.backup_review_saved), tones.kept) Text("→", fontSize = 24.sp, color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(horizontal = 18.dp)) - KeyColumn(key.after, stringRes(R.string.backup_review_new), tones.changed) + KeyColumn(key.after, stringRes(Res.string.backup_review_new), tones.changed) } if (key.after != null) { Row( @@ -950,7 +1011,7 @@ private fun LazyListScope.nutzapItems( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Icon(symbol = MaterialSymbols.Warning, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp)) - Text(stringRes(R.string.backup_review_key_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed) + Text(stringRes(Res.string.backup_review_key_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed) } } } @@ -976,10 +1037,10 @@ private fun LazyListScope.nutzapItems( } + diff.mints.added.map { Triple(it.mintUrl, it.units.joinToString(), LaneState.ADDED) } if (mintRows.isNotEmpty()) { - item(key = "nutzap-mints", contentType = "caption") { SectionCaption(stringRes(R.string.backup_entry_mint), Pad.padding(top = 18.dp, bottom = 6.dp)) } + item(key = "nutzap-mints", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_entry_mint), Pad.padding(top = 18.dp, bottom = 6.dp)) } items(mintRows, key = { "mint-" + it.first }, contentType = { "mint" }) { (url, units, state) -> MintRow(url, units, state) } } - genericDiffItems(buildRows(presentation, skipGroups = setOf(R.string.backup_entry_mint, R.string.backup_entry_nutzap_key)), accountViewModel, nav) + genericDiffItems(buildRows(presentation, skipGroups = setOf(Res.string.backup_entry_mint, Res.string.backup_entry_nutzap_key)), accountViewModel, nav) } @Composable @@ -993,7 +1054,7 @@ private fun KeyColumn( KeyFingerprint(hex, color) } else { Box(Modifier.size(96.dp).clip(RoundedCornerShape(18.dp)).border(2.dp, color, RoundedCornerShape(18.dp)), contentAlignment = Alignment.Center) { - Text(stringRes(R.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color) + Text(stringRes(Res.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color) } } StatusTag(label, color) @@ -1010,9 +1071,9 @@ private fun MintRow( val (color, tag) = when (state) { LaneState.KEPT -> MaterialTheme.colorScheme.onSurface to null - LaneState.DROPPED -> tones.removed to R.string.backup_review_dropped_tag - LaneState.ADDED -> tones.added to R.string.backup_review_new_tag - LaneState.DEMOTED -> tones.changed to R.string.backup_review_units_tag + LaneState.DROPPED -> tones.removed to Res.string.backup_review_dropped_tag + LaneState.ADDED -> tones.added to Res.string.backup_review_new_tag + LaneState.DEMOTED -> tones.changed to Res.string.backup_review_units_tag } Row( Pad @@ -1083,21 +1144,21 @@ private fun LazyListScope.groupItems( StatTile( diff.groups.removed.size .toString(), - stringRes(R.string.backup_review_left), + stringRes(Res.string.backup_review_left), tones.removed, Modifier.weight(1f), ) StatTile( diff.groups.added.size .toString(), - stringRes(R.string.backup_review_joined), + stringRes(Res.string.backup_review_joined), tones.added, Modifier.weight(1f), ) StatTile( diff.groups.changed.size .toString(), - stringRes(R.string.backup_review_renamed), + stringRes(Res.string.backup_review_renamed), tones.changed, Modifier.weight(1f), ) @@ -1165,7 +1226,7 @@ private fun GroupTileContent( Column { Text(name, style = MaterialTheme.typography.titleSmall, maxLines = 1, overflow = TextOverflow.Ellipsis) Text( - tile.oldName?.let { stringRes(R.string.backup_review_was_named, it) } ?: tile.group.relayUrl + tile.oldName?.let { stringRes(Res.string.backup_review_was_named, it) } ?: tile.group.relayUrl .removePrefix("wss://") .removeSuffix("/"), style = MaterialTheme.typography.labelSmall, @@ -1176,9 +1237,9 @@ private fun GroupTileContent( } } when (tile.state) { - LaneState.DROPPED -> StatusTag(stringRes(R.string.backup_review_left_tag), tones.removed, Modifier.align(Alignment.TopEnd)) - LaneState.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), tones.added, Modifier.align(Alignment.TopEnd)) - LaneState.DEMOTED -> StatusTag(stringRes(R.string.backup_review_renamed_tag), tones.changed, Modifier.align(Alignment.TopEnd)) + LaneState.DROPPED -> StatusTag(stringRes(Res.string.backup_review_left_tag), tones.removed, Modifier.align(Alignment.TopEnd)) + LaneState.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), tones.added, Modifier.align(Alignment.TopEnd)) + LaneState.DEMOTED -> StatusTag(stringRes(Res.string.backup_review_renamed_tag), tones.changed, Modifier.align(Alignment.TopEnd)) LaneState.KEPT -> {} } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt index 55473cce27..b2e854c06b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt @@ -48,13 +48,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -63,6 +61,53 @@ import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflic import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.routeFor +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.backup_review_allowed_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_concord_body +import com.vitorpamplona.amethyst.commons.resources.backup_review_demoted_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_added +import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_changed +import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_cleared +import com.vitorpamplona.amethyst.commons.resources.backup_review_feed +import com.vitorpamplona.amethyst.commons.resources.backup_review_hashtags_you_follow +import com.vitorpamplona.amethyst.commons.resources.backup_review_joined +import com.vitorpamplona.amethyst.commons.resources.backup_review_kept +import com.vitorpamplona.amethyst.commons.resources.backup_review_left +import com.vitorpamplona.amethyst.commons.resources.backup_review_left_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_listed_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_moved_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_new +import com.vitorpamplona.amethyst.commons.resources.backup_review_new_count_caption +import com.vitorpamplona.amethyst.commons.resources.backup_review_new_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_newly_blocked +import com.vitorpamplona.amethyst.commons.resources.backup_review_no_longer_blocked +import com.vitorpamplona.amethyst.commons.resources.backup_review_offer +import com.vitorpamplona.amethyst.commons.resources.backup_review_offers_gone +import com.vitorpamplona.amethyst.commons.resources.backup_review_payment_targets_gone +import com.vitorpamplona.amethyst.commons.resources.backup_review_payments_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_places_gone +import com.vitorpamplona.amethyst.commons.resources.backup_review_places_you_follow +import com.vitorpamplona.amethyst.commons.resources.backup_review_receiving_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_saved +import com.vitorpamplona.amethyst.commons.resources.backup_review_searching_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_service +import com.vitorpamplona.amethyst.commons.resources.backup_review_service_followers +import com.vitorpamplona.amethyst.commons.resources.backup_review_service_rank +import com.vitorpamplona.amethyst.commons.resources.backup_review_service_topics +import com.vitorpamplona.amethyst.commons.resources.backup_review_services_changed +import com.vitorpamplona.amethyst.commons.resources.backup_review_services_changed_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_still_blocked +import com.vitorpamplona.amethyst.commons.resources.backup_review_storing_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_topics_gone +import com.vitorpamplona.amethyst.commons.resources.backup_review_trust_warning +import com.vitorpamplona.amethyst.commons.resources.backup_review_trusted_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_unblocked +import com.vitorpamplona.amethyst.commons.resources.backup_review_unblocked_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_review_untrusted_tag +import com.vitorpamplona.amethyst.commons.resources.backup_review_wallet_body +import com.vitorpamplona.amethyst.commons.resources.backup_type_communities +import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings @@ -118,6 +163,9 @@ import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEve import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.pluralStringResource /** * Per-event layouts for the account's other lists: each relay list framed by what it is @@ -142,8 +190,8 @@ internal fun LazyListScope.listDiffItems( is TrustProviderListDiff -> trustProviderItems(diff, conflict, accountViewModel, nav) is PaymentTargetsDiff -> paymentTargetItems(diff, conflict) is Bolt12OfferListDiff -> offerItems(diff, conflict) - is CashuWalletDiff -> encryptedOnlyItems(MaterialSymbols.AccountBalanceWallet, diff.wallet, R.string.backup_review_wallet_body) - is ConcordCommunityListDiff -> encryptedOnlyItems(MaterialSymbols.Groups, diff.communities, R.string.backup_review_concord_body) + is CashuWalletDiff -> encryptedOnlyItems(MaterialSymbols.AccountBalanceWallet, diff.wallet, Res.string.backup_review_wallet_body) + is ConcordCommunityListDiff -> encryptedOnlyItems(MaterialSymbols.Groups, diff.communities, Res.string.backup_review_concord_body) else -> return false } return true @@ -193,15 +241,15 @@ private fun fateColor(fate: ItemFate): Color { private fun LazyListScope.countTiles( items: List>, - removedCaption: Int, - addedCaption: Int, + removedCaption: StringResource, + addedCaption: StringResource, ) { item(key = "count-tiles", contentType = "tiles") { val tones = conflictTones() Row(Pad.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { StatTile(items.count { it.second == ItemFate.DROPPED }.toString(), stringRes(removedCaption), tones.removed, Modifier.weight(1f)) StatTile(items.count { it.second == ItemFate.ADDED }.toString(), stringRes(addedCaption), tones.added, Modifier.weight(1f)) - StatTile(items.count { it.second == ItemFate.KEPT || it.second == ItemFate.CHANGED }.toString(), stringRes(R.string.backup_review_kept), tones.kept, Modifier.weight(1f)) + StatTile(items.count { it.second == ItemFate.KEPT || it.second == ItemFate.CHANGED }.toString(), stringRes(Res.string.backup_review_kept), tones.kept, Modifier.weight(1f)) } } } @@ -218,20 +266,20 @@ private fun LazyListScope.privateItemsCard(change: ContentChange) { /** How a relay list's rows are presented: its icon and what a kept or dropped relay is called. */ private class RelayFraming( val icon: MaterialSymbol, - val keptTag: Int, - val droppedTag: Int = R.string.backup_review_dropped_tag, + val keptTag: StringResource, + val droppedTag: StringResource = Res.string.backup_review_dropped_tag, ) private fun framingOf(type: BackupEventType): RelayFraming = when (type) { - BackupEventType.DM_RELAYS -> RelayFraming(MaterialSymbols.Mail, R.string.backup_review_receiving_tag) - BackupEventType.KEY_PACKAGE_RELAYS -> RelayFraming(MaterialSymbols.Key, R.string.backup_review_listed_tag) - BackupEventType.SEARCH_RELAYS -> RelayFraming(MaterialSymbols.Search, R.string.backup_review_searching_tag) - BackupEventType.INDEXER_RELAYS -> RelayFraming(MaterialSymbols.Dns, R.string.backup_review_listed_tag) - BackupEventType.RELAY_FEEDS -> RelayFraming(MaterialSymbols.CellTower, R.string.backup_review_listed_tag) - BackupEventType.PRIVATE_OUTBOX_RELAYS -> RelayFraming(MaterialSymbols.Lock, R.string.backup_review_storing_tag) - BackupEventType.TRUSTED_RELAYS -> RelayFraming(MaterialSymbols.Shield, R.string.backup_review_trusted_tag, R.string.backup_review_untrusted_tag) - else -> RelayFraming(MaterialSymbols.Dns, R.string.backup_review_listed_tag) + BackupEventType.DM_RELAYS -> RelayFraming(MaterialSymbols.Mail, Res.string.backup_review_receiving_tag) + BackupEventType.KEY_PACKAGE_RELAYS -> RelayFraming(MaterialSymbols.Key, Res.string.backup_review_listed_tag) + BackupEventType.SEARCH_RELAYS -> RelayFraming(MaterialSymbols.Search, Res.string.backup_review_searching_tag) + BackupEventType.INDEXER_RELAYS -> RelayFraming(MaterialSymbols.Dns, Res.string.backup_review_listed_tag) + BackupEventType.RELAY_FEEDS -> RelayFraming(MaterialSymbols.CellTower, Res.string.backup_review_listed_tag) + BackupEventType.PRIVATE_OUTBOX_RELAYS -> RelayFraming(MaterialSymbols.Lock, Res.string.backup_review_storing_tag) + BackupEventType.TRUSTED_RELAYS -> RelayFraming(MaterialSymbols.Shield, Res.string.backup_review_trusted_tag, Res.string.backup_review_untrusted_tag) + else -> RelayFraming(MaterialSymbols.Dns, Res.string.backup_review_listed_tag) } private fun savedRelaysOf(event: Event): List = @@ -277,8 +325,8 @@ private fun RelayFateRow( when (fate) { ItemFate.KEPT -> framing.keptTag ItemFate.DROPPED -> framing.droppedTag - ItemFate.ADDED -> R.string.backup_review_new_tag - ItemFate.CHANGED -> R.string.backup_review_demoted_tag + ItemFate.ADDED -> Res.string.backup_review_new_tag + ItemFate.CHANGED -> Res.string.backup_review_demoted_tag } Row( Pad @@ -320,14 +368,14 @@ private fun LazyListScope.blockedRelayItems( Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) { Icon(symbol = MaterialSymbols.LockOpen, contentDescription = null, tint = tones.changed, modifier = Modifier.size(44.dp)) Column { - Text(stringRes(R.string.backup_review_unblocked, unblocked.size.toString()), fontSize = 30.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold, color = tones.changed) - Text(stringRes(R.string.backup_review_unblocked_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) + Text(stringRes(Res.string.backup_review_unblocked, unblocked.size.toString()), fontSize = 30.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold, color = tones.changed) + Text(stringRes(Res.string.backup_review_unblocked_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) } } } } if (unblocked.isNotEmpty()) { - item(key = "unblocked-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_review_no_longer_blocked), Pad.padding(top = 18.dp)) } + item(key = "unblocked-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_review_no_longer_blocked), Pad.padding(top = 18.dp)) } items(unblocked, key = { "unblocked-" + it.url }, contentType = { "relay-row" }) { url -> val tones = conflictTones() Row( @@ -346,12 +394,12 @@ private fun LazyListScope.blockedRelayItems( Icon(symbol = MaterialSymbols.LockOpen, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp)) } Text(url.url.removePrefix("wss://").removeSuffix("/"), modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis) - StatusTag(stringRes(R.string.backup_review_allowed_tag), tones.changed) + StatusTag(stringRes(Res.string.backup_review_allowed_tag), tones.changed) } } } if (newlyBlocked.isNotEmpty()) { - item(key = "newly-blocked-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_review_newly_blocked), Pad.padding(top = 18.dp)) } + item(key = "newly-blocked-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_review_newly_blocked), Pad.padding(top = 18.dp)) } items(newlyBlocked, key = { "newly-blocked-" + it.url }, contentType = { "relay-row" }) { url -> RelayFateRow(url, ItemFate.ADDED, framingOf(BackupEventType.BLOCKED_RELAYS), nav) } @@ -359,7 +407,7 @@ private fun LazyListScope.blockedRelayItems( if (stillBlocked.isNotEmpty()) { item(key = "still-blocked", contentType = "chips") { Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - SectionCaption(stringRes(R.string.backup_review_still_blocked)) + SectionCaption(stringRes(Res.string.backup_review_still_blocked)) FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { stillBlocked.forEach { url -> Row( @@ -392,7 +440,7 @@ private fun LazyListScope.spaceGrid( key: (T) -> String, content: @Composable (T, ItemFate, Modifier) -> Unit, ) { - countTiles(items, R.string.backup_review_left, R.string.backup_review_joined) + countTiles(items, Res.string.backup_review_left, Res.string.backup_review_joined) items(items.chunked(2), key = { keyPrefix + key(it.first().first) }, contentType = { "space-row" }) { row -> Row(Pad.padding(top = 10.dp).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { row.forEach { (item, fate) -> content(item, fate, Modifier.weight(1f)) } @@ -436,9 +484,9 @@ private fun SpaceTile( } val tag = when (fate) { - ItemFate.DROPPED -> R.string.backup_review_left_tag to tones.removed - ItemFate.ADDED -> R.string.backup_review_new_tag to tones.added - ItemFate.CHANGED -> R.string.backup_review_moved_tag to tones.changed + ItemFate.DROPPED -> Res.string.backup_review_left_tag to tones.removed + ItemFate.ADDED -> Res.string.backup_review_new_tag to tones.added + ItemFate.CHANGED -> Res.string.backup_review_moved_tag to tones.changed ItemFate.KEPT -> null } tag?.let { (res, color) -> StatusTag(stringRes(res), color, Modifier.align(Alignment.TopEnd)) } @@ -459,7 +507,7 @@ private fun LazyListScope.publicChatItems( val current = state?.channel ?: channel SpaceTile( name = current.toBestDisplayName(), - subtitle = stringRes(R.string.backup_type_public_chats), + subtitle = stringRes(Res.string.backup_type_public_chats), robot = tag.eventId, picture = channel.profilePicture(), fate = fate, @@ -525,7 +573,7 @@ private fun AddressableSpaceTile( } SpaceTile( name = name ?: address.dTag.ifBlank { address.toValue().toShortDisplay() }, - subtitle = stringRes(if (address.kind == AppDefinitionEvent.KIND) R.string.backup_review_feed else R.string.backup_type_communities), + subtitle = stringRes(if (address.kind == AppDefinitionEvent.KIND) Res.string.backup_review_feed else Res.string.backup_type_communities), robot = address.toValue(), picture = picture, fate = fate, @@ -543,7 +591,7 @@ private fun LazyListScope.ephemeralRoomItems( ) { val saved = (conflict.saved as? EphemeralChatListEvent)?.publicRooms().orEmpty() val rooms = fates(saved, diff.rooms) { it } - countTiles(rooms, R.string.backup_review_left, R.string.backup_review_joined) + countTiles(rooms, Res.string.backup_review_left, Res.string.backup_review_joined) items(rooms, key = { "room-" + it.first.toKey() }, contentType = { "room-row" }) { (room, fate) -> RoomRow(room, fate, nav) } privateItemsCard(diff.privateItems) } @@ -582,8 +630,8 @@ private fun RoomRow( ) } when (fate) { - ItemFate.DROPPED -> StatusTag(stringRes(R.string.backup_review_left_tag), color) - ItemFate.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), color) + ItemFate.DROPPED -> StatusTag(stringRes(Res.string.backup_review_left_tag), color) + ItemFate.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), color) else -> {} } } @@ -602,11 +650,11 @@ private fun LazyListScope.topicItems( nav: INav, ) { val topics = fates(saved, diff) { it.lowercase() } - countTiles(topics, if (isPlace) R.string.backup_review_places_gone else R.string.backup_review_topics_gone, R.string.backup_review_new_count_caption) + countTiles(topics, if (isPlace) Res.string.backup_review_places_gone else Res.string.backup_review_topics_gone, Res.string.backup_review_new_count_caption) item(key = "topic-cloud", contentType = "cloud") { val tones = conflictTones() Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - SectionCaption(stringRes(if (isPlace) R.string.backup_review_places_you_follow else R.string.backup_review_hashtags_you_follow)) + SectionCaption(stringRes(if (isPlace) Res.string.backup_review_places_you_follow else Res.string.backup_review_hashtags_you_follow)) FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), itemVerticalAlignment = Alignment.CenterVertically) { topics.forEach { (topic, fate) -> val color = fateColor(fate) @@ -706,15 +754,15 @@ private fun LazyListScope.trustProviderItems( Icon(symbol = MaterialSymbols.Group, contentDescription = null, tint = tones.changed) } Column { - Text(stringRes(R.string.backup_review_services_changed, affected.toString(), services.size.toString()), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold) - Text(stringRes(R.string.backup_review_services_changed_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) + Text(stringRes(Res.string.backup_review_services_changed, affected.toString(), services.size.toString()), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold) + Text(stringRes(Res.string.backup_review_services_changed_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) } } } } item(key = "trust-header", contentType = "table-header") { Row(Pad.padding(top = 16.dp, bottom = 4.dp).padding(horizontal = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - listOf(R.string.backup_review_service, R.string.backup_review_saved, R.string.backup_review_new).forEachIndexed { index, res -> + listOf(Res.string.backup_review_service, Res.string.backup_review_saved, Res.string.backup_review_new).forEachIndexed { index, res -> Text(stringRes(res).uppercase(), Modifier.weight(if (index == 0) 1.1f else 1f), style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.placeholderText) } } @@ -732,7 +780,7 @@ private fun LazyListScope.trustProviderItems( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Icon(symbol = MaterialSymbols.Warning, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp)) - Text(stringRes(R.string.backup_review_trust_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed) + Text(stringRes(Res.string.backup_review_trust_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed) } } privateItemsCard(diff.privateItems) @@ -771,9 +819,9 @@ private fun ServiceTableRow( @Composable private fun serviceLabel(type: String): String = when (type) { - "rank" -> stringRes(R.string.backup_review_service_rank) - "followers" -> stringRes(R.string.backup_review_service_followers) - "t" -> stringRes(R.string.backup_review_service_topics) + "rank" -> stringRes(Res.string.backup_review_service_rank) + "followers" -> stringRes(Res.string.backup_review_service_followers) + "t" -> stringRes(Res.string.backup_review_service_topics) else -> type.replace('_', ' ').replaceFirstChar { it.uppercase() } } @@ -826,7 +874,7 @@ private fun LazyListScope.paymentTargetItems( ) { val saved = (conflict.saved as? PaymentTargetsEvent)?.paymentTargets().orEmpty() val targets = fates(saved, diff.targets) { it } - moneyHero(targets.count { it.second == ItemFate.DROPPED }, R.plurals.backup_review_payment_targets_gone) + moneyHero(targets.count { it.second == ItemFate.DROPPED }, Res.plurals.backup_review_payment_targets_gone) items(targets, key = { "target-" + it.first.type + ":" + it.first.authority }, contentType = { "money-row" }) { (target, fate) -> PaymentTargetRow(target, fate) } } @@ -836,15 +884,15 @@ private fun LazyListScope.offerItems( ) { val saved = (conflict.saved as? Bolt12OfferListEvent)?.offers().orEmpty() val offers = fates(saved, diff.offers) { it } - moneyHero(offers.count { it.second == ItemFate.DROPPED }, R.plurals.backup_review_offers_gone) + moneyHero(offers.count { it.second == ItemFate.DROPPED }, Res.plurals.backup_review_offers_gone) items(offers, key = { "offer-" + it.first }, contentType = { "money-row" }) { (offer, fate) -> - MoneyRow(stringRes(R.string.backup_review_offer), offer.toShortDisplay(prefixSize = 4), fate) + MoneyRow(stringRes(Res.string.backup_review_offer), offer.toShortDisplay(prefixSize = 4), fate) } } private fun LazyListScope.moneyHero( dropped: Int, - headline: Int, + headline: PluralStringResource, ) { item(key = "money-hero", contentType = "hero") { val tones = conflictTones() @@ -856,7 +904,7 @@ private fun LazyListScope.moneyHero( } Column { Text(pluralStringResource(headline, dropped, dropped), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold, color = color) - Text(stringRes(R.string.backup_review_payments_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) + Text(stringRes(Res.string.backup_review_payments_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText) } } } @@ -902,8 +950,8 @@ private fun MoneyRow( ) } when (fate) { - ItemFate.DROPPED -> StatusTag(stringRes(R.string.backup_review_dropped_tag), color) - ItemFate.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), color) + ItemFate.DROPPED -> StatusTag(stringRes(Res.string.backup_review_dropped_tag), color) + ItemFate.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), color) else -> {} } } @@ -916,15 +964,15 @@ private fun MoneyRow( private fun LazyListScope.encryptedOnlyItems( icon: MaterialSymbol, change: ContentChange, - body: Int, + body: StringResource, ) { item(key = "encrypted-hero", contentType = "hero") { val tones = conflictTones() val (color, headline) = when (change) { - ContentChange.CLEARED -> tones.removed to R.string.backup_review_encrypted_cleared - ContentChange.ADDED -> tones.added to R.string.backup_review_encrypted_added - else -> tones.changed to R.string.backup_review_encrypted_changed + ContentChange.CLEARED -> tones.removed to Res.string.backup_review_encrypted_cleared + ContentChange.ADDED -> tones.added to Res.string.backup_review_encrypted_added + else -> tones.changed to Res.string.backup_review_encrypted_changed } Column(Pad.padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) { TintedPanel(color.copy(alpha = 0.12f)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt index 58195a5d31..ff28dfa6c3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt @@ -23,9 +23,98 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.backups import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalResources -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_read_only +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_read_write +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_write_only +import com.vitorpamplona.amethyst.commons.resources.backup_entry_algo_feed +import com.vitorpamplona.amethyst.commons.resources.backup_entry_chat_room +import com.vitorpamplona.amethyst.commons.resources.backup_entry_community +import com.vitorpamplona.amethyst.commons.resources.backup_entry_hashtag +import com.vitorpamplona.amethyst.commons.resources.backup_entry_identity_claim +import com.vitorpamplona.amethyst.commons.resources.backup_entry_location +import com.vitorpamplona.amethyst.commons.resources.backup_entry_mint +import com.vitorpamplona.amethyst.commons.resources.backup_entry_nutzap_key +import com.vitorpamplona.amethyst.commons.resources.backup_entry_offer +import com.vitorpamplona.amethyst.commons.resources.backup_entry_other +import com.vitorpamplona.amethyst.commons.resources.backup_entry_payment_target +import com.vitorpamplona.amethyst.commons.resources.backup_entry_person +import com.vitorpamplona.amethyst.commons.resources.backup_entry_profile_field +import com.vitorpamplona.amethyst.commons.resources.backup_entry_public_chat +import com.vitorpamplona.amethyst.commons.resources.backup_entry_relay +import com.vitorpamplona.amethyst.commons.resources.backup_entry_relay_group +import com.vitorpamplona.amethyst.commons.resources.backup_entry_thread +import com.vitorpamplona.amethyst.commons.resources.backup_entry_trust_provider +import com.vitorpamplona.amethyst.commons.resources.backup_entry_word +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_about +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_banner +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_birthday +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_bot +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_clink_offer +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_display_name +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud06 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud16 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_name +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_nip05 +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_picture +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_pronouns +import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_website +import com.vitorpamplona.amethyst.commons.resources.backup_type_app_settings +import com.vitorpamplona.amethyst.commons.resources.backup_type_app_settings_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_blocked_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_blocked_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_bolt12_offers +import com.vitorpamplona.amethyst.commons.resources.backup_type_bolt12_offers_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_cashu_wallet +import com.vitorpamplona.amethyst.commons.resources.backup_type_cashu_wallet_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_communities +import com.vitorpamplona.amethyst.commons.resources.backup_type_communities_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_concord_communities +import com.vitorpamplona.amethyst.commons.resources.backup_type_concord_communities_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_dm_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_dm_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_ephemeral_chats +import com.vitorpamplona.amethyst.commons.resources.backup_type_ephemeral_chats_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_favorite_algo_feeds +import com.vitorpamplona.amethyst.commons.resources.backup_type_favorite_algo_feeds_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_follow_list +import com.vitorpamplona.amethyst.commons.resources.backup_type_follow_list_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_geohashes +import com.vitorpamplona.amethyst.commons.resources.backup_type_geohashes_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_hashtags +import com.vitorpamplona.amethyst.commons.resources.backup_type_hashtags_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_indexer_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_indexer_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_key_package_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_key_package_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_mute_list +import com.vitorpamplona.amethyst.commons.resources.backup_type_mute_list_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_nutzap_info +import com.vitorpamplona.amethyst.commons.resources.backup_type_nutzap_info_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_other +import com.vitorpamplona.amethyst.commons.resources.backup_type_other_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_outbox_inbox_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_outbox_inbox_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_payment_targets +import com.vitorpamplona.amethyst.commons.resources.backup_type_payment_targets_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_private_outbox_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_private_outbox_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_profile +import com.vitorpamplona.amethyst.commons.resources.backup_type_profile_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats +import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_feeds +import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_feeds_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_groups +import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_groups_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_search_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_search_relays_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers +import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers_explainer +import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays +import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays_explainer +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListDiff import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListDiff @@ -61,6 +150,7 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataDiff import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff +import org.jetbrains.compose.resources.StringResource private const val MAX_VALUE_LENGTH = 80 @@ -168,7 +258,7 @@ sealed interface ReviewItem { /** One labelled group of differences (people, relays, profile fields…) of a conflict. */ @Immutable class DiffGroup( - val label: Int, + val label: StringResource, val removed: List, val added: List, val changed: List, @@ -189,7 +279,7 @@ class DiffPresentation( /** A group from a [ListDiff] of an event's parsed items. */ private fun listGroup( - label: Int, + label: StringResource, diff: ListDiff, item: (T) -> ReviewItem, change: (ItemChange) -> ReviewItem, @@ -197,7 +287,7 @@ private fun listGroup( /** A group whose items can't change, only be added or removed (hashtags, plain relays…). */ private fun listGroup( - label: Int, + label: StringResource, diff: ListDiff, item: (T) -> ReviewItem, ) = listGroup(label, diff, item) { item(it.after) } @@ -212,7 +302,7 @@ private inline fun ListDiff<*>.only() = /** A group from single-valued fields: a field set only after is added, only before is removed. */ private fun fieldGroup( - label: Int, + label: StringResource, fields: List?>>, ): DiffGroup { val removed = mutableListOf() @@ -242,14 +332,38 @@ private fun arrow( */ @Composable fun rememberPresentation(diff: EventDiff): DiffPresentation { - val resources = LocalResources.current - return remember(diff, resources) { presentationOf(diff) { resources.getString(it) } } + // Compose resources resolve only in composition (or suspended), so the few labels + // [presentationOf] writes into item text are resolved here and handed in. The map + // compares by value, so a recomposition with the same locale keeps the cached result. + val labels = PRESENTATION_LABELS.associateWith { stringRes(it) } + return remember(diff, labels) { presentationOf(diff) { labels.getValue(it) } } } +/** Every label [presentationOf] resolves through its `str` parameter. */ +private val PRESENTATION_LABELS = + listOf( + Res.string.backup_profile_field_name, + Res.string.backup_profile_field_display_name, + Res.string.backup_profile_field_about, + Res.string.backup_profile_field_picture, + Res.string.backup_profile_field_banner, + Res.string.backup_profile_field_website, + Res.string.backup_profile_field_nip05, + Res.string.backup_profile_field_lud16, + Res.string.backup_profile_field_lud06, + Res.string.backup_profile_field_clink_offer, + Res.string.backup_profile_field_pronouns, + Res.string.backup_profile_field_birthday, + Res.string.backup_profile_field_bot, + Res.string.backup_conflict_relay_read_write, + Res.string.backup_conflict_relay_read_only, + Res.string.backup_conflict_relay_write_only, + ) + /** Turns each event's own diff into typed, labelled groups. [str] resolves the labels. */ fun presentationOf( diff: EventDiff, - str: (Int) -> String, + str: (StringResource) -> String, ): DiffPresentation { val (groups, content) = when (diff) { @@ -258,31 +372,31 @@ fun presentationOf( val bot = diff.bot?.let { ValueChange(it.before?.toString(), it.after?.toString()) } listOf( fieldGroup( - R.string.backup_entry_profile_field, + Res.string.backup_entry_profile_field, listOf( - str(R.string.backup_profile_field_name) to diff.name, - str(R.string.backup_profile_field_display_name) to diff.displayName, - str(R.string.backup_profile_field_about) to diff.about, - str(R.string.backup_profile_field_picture) to diff.picture, - str(R.string.backup_profile_field_banner) to diff.banner, - str(R.string.backup_profile_field_website) to diff.website, - str(R.string.backup_profile_field_nip05) to diff.nip05, - str(R.string.backup_profile_field_lud16) to diff.lud16, - str(R.string.backup_profile_field_lud06) to diff.lud06, - str(R.string.backup_profile_field_clink_offer) to diff.clinkOffer, - str(R.string.backup_profile_field_pronouns) to diff.pronouns, - str(R.string.backup_profile_field_birthday) to birthday, - str(R.string.backup_profile_field_bot) to bot, + str(Res.string.backup_profile_field_name) to diff.name, + str(Res.string.backup_profile_field_display_name) to diff.displayName, + str(Res.string.backup_profile_field_about) to diff.about, + str(Res.string.backup_profile_field_picture) to diff.picture, + str(Res.string.backup_profile_field_banner) to diff.banner, + str(Res.string.backup_profile_field_website) to diff.website, + str(Res.string.backup_profile_field_nip05) to diff.nip05, + str(Res.string.backup_profile_field_lud16) to diff.lud16, + str(Res.string.backup_profile_field_lud06) to diff.lud06, + str(Res.string.backup_profile_field_clink_offer) to diff.clinkOffer, + str(Res.string.backup_profile_field_pronouns) to diff.pronouns, + str(Res.string.backup_profile_field_birthday) to birthday, + str(Res.string.backup_profile_field_bot) to bot, ), ), listGroup( - R.string.backup_entry_identity_claim, + Res.string.backup_entry_identity_claim, diff.identityClaims, { ReviewItem.Text(it.platformIdentity()) }, { ReviewItem.Text(it.after.platformIdentity(), arrow(it.before.proof, it.after.proof)) }, ), listGroup( - R.string.backup_entry_other, + Res.string.backup_entry_other, diff.otherFields, { ReviewItem.Text(it.first, clip(it.second)) }, { ReviewItem.Text(it.after.first, clip(it.before.second) + " → " + clip(it.after.second)) }, @@ -292,7 +406,7 @@ fun presentationOf( is ContactListDiff -> listOf( listGroup( - R.string.backup_entry_person, + Res.string.backup_entry_person, diff.follows, { ReviewItem.Person(it.pubKey, it.petname) }, { @@ -308,32 +422,32 @@ fun presentationOf( ) to ContentChange.NONE is MuteListDiff -> listOf( - listGroup(R.string.backup_entry_person, diff.publicMutes.only(), { ReviewItem.Person(it.pubKey) }), - listGroup(R.string.backup_entry_word, diff.publicMutes.only(), { ReviewItem.Text("\"" + it.word + "\"") }), - listGroup(R.string.backup_entry_hashtag, diff.publicMutes.only(), { ReviewItem.Text("#" + it.hashtag) }), - listGroup(R.string.backup_entry_thread, diff.publicMutes.only(), { ReviewItem.Thread(it.eventId) }), + listGroup(Res.string.backup_entry_person, diff.publicMutes.only(), { ReviewItem.Person(it.pubKey) }), + listGroup(Res.string.backup_entry_word, diff.publicMutes.only(), { ReviewItem.Text("\"" + it.word + "\"") }), + listGroup(Res.string.backup_entry_hashtag, diff.publicMutes.only(), { ReviewItem.Text("#" + it.hashtag) }), + listGroup(Res.string.backup_entry_thread, diff.publicMutes.only(), { ReviewItem.Thread(it.eventId) }), ) to diff.privateItems is AdvertisedRelayListDiff -> { val types = mapOf( - AdvertisedRelayType.BOTH to str(R.string.backup_conflict_relay_read_write), - AdvertisedRelayType.READ to str(R.string.backup_conflict_relay_read_only), - AdvertisedRelayType.WRITE to str(R.string.backup_conflict_relay_write_only), + AdvertisedRelayType.BOTH to str(Res.string.backup_conflict_relay_read_write), + AdvertisedRelayType.READ to str(Res.string.backup_conflict_relay_read_only), + AdvertisedRelayType.WRITE to str(Res.string.backup_conflict_relay_write_only), ) listOf( listGroup( - R.string.backup_entry_relay, + Res.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it.relayUrl, types[it.type]) }, { ReviewItem.Relay(it.after.relayUrl, arrow(types[it.before.type], types[it.after.type])) }, ), ) to ContentChange.NONE } - is RelayListDiff -> listOf(listGroup(R.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) })) to diff.privateRelays + is RelayListDiff -> listOf(listGroup(Res.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) })) to diff.privateRelays is ChannelListDiff -> listOf( listGroup( - R.string.backup_entry_public_chat, + Res.string.backup_entry_public_chat, diff.channels, { ReviewItem.PublicChat(it.eventId) }, { ReviewItem.PublicChat(it.after.eventId, arrow(it.before.relay?.url, it.after.relay?.url)) }, @@ -342,28 +456,28 @@ fun presentationOf( is CommunityListDiff -> listOf( listGroup( - R.string.backup_entry_community, + Res.string.backup_entry_community, diff.communities, { ReviewItem.Addressable(it.address) }, { ReviewItem.Addressable(it.after.address, arrow(it.before.relayHint?.url, it.after.relayHint?.url)) }, ), ) to diff.privateItems - is HashtagListDiff -> listOf(listGroup(R.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems - is GeohashListDiff -> listOf(listGroup(R.string.backup_entry_location, diff.geohashes, { ReviewItem.Text(it) })) to diff.privateItems + is HashtagListDiff -> listOf(listGroup(Res.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems + is GeohashListDiff -> listOf(listGroup(Res.string.backup_entry_location, diff.geohashes, { ReviewItem.Text(it) })) to diff.privateItems is FavoriteAlgoFeedsListDiff -> listOf( listGroup( - R.string.backup_entry_algo_feed, + Res.string.backup_entry_algo_feed, diff.feeds, { ReviewItem.Addressable(it.address) }, { ReviewItem.Addressable(it.after.address, arrow(it.before.relayHint?.url, it.after.relayHint?.url)) }, ), ) to diff.privateItems - is EphemeralChatListDiff -> listOf(listGroup(R.string.backup_entry_chat_room, diff.rooms, { ReviewItem.ChatRoom(it) })) to diff.privateItems + is EphemeralChatListDiff -> listOf(listGroup(Res.string.backup_entry_chat_room, diff.rooms, { ReviewItem.ChatRoom(it) })) to diff.privateItems is SimpleGroupListDiff -> listOf( listGroup( - R.string.backup_entry_relay_group, + Res.string.backup_entry_relay_group, diff.groups, { ReviewItem.Text(it.name ?: it.groupId, it.relayUrl) }, { ReviewItem.Text(it.after.name ?: it.after.groupId, arrow(it.before.name, it.after.name)) }, @@ -372,7 +486,7 @@ fun presentationOf( is TrustProviderListDiff -> listOf( listGroup( - R.string.backup_entry_trust_provider, + Res.string.backup_entry_trust_provider, diff.providers, { ReviewItem.Person(it.pubkey, it.service.type) }, { ReviewItem.Person(it.after.pubkey, it.after.service.type + ": " + arrow(it.before.relayUrl.url, it.after.relayUrl.url)) }, @@ -381,19 +495,19 @@ fun presentationOf( is NutzapInfoDiff -> listOf( listGroup( - R.string.backup_entry_mint, + Res.string.backup_entry_mint, diff.mints, { ReviewItem.Text(it.mintUrl, it.units.joinToString().ifEmpty { null }) }, { ReviewItem.Text(it.after.mintUrl, arrow(it.before.units.joinToString(), it.after.units.joinToString())) }, ), - listGroup(R.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) }), + listGroup(Res.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) }), nutzapKeyGroup(diff.p2pkPubkey), ) to ContentChange.NONE is PaymentTargetsDiff -> listOf( - listGroup(R.string.backup_entry_payment_target, diff.targets, { ReviewItem.Text(it.type, clip(it.authority)) }), + listGroup(Res.string.backup_entry_payment_target, diff.targets, { ReviewItem.Text(it.type, clip(it.authority)) }), ) to ContentChange.NONE - is Bolt12OfferListDiff -> listOf(listGroup(R.string.backup_entry_offer, diff.offers, { ReviewItem.Text(clip(it)) })) to ContentChange.NONE + is Bolt12OfferListDiff -> listOf(listGroup(Res.string.backup_entry_offer, diff.offers, { ReviewItem.Text(clip(it)) })) to ContentChange.NONE is CashuWalletDiff -> emptyList() to diff.wallet is ConcordCommunityListDiff -> emptyList() to diff.communities is AppSpecificDataDiff -> emptyList() to diff.data @@ -404,13 +518,13 @@ fun presentationOf( private fun nutzapKeyGroup(change: ValueChange?): DiffGroup { val none = emptyList() - if (change == null) return DiffGroup(R.string.backup_entry_nutzap_key, none, none, none) + if (change == null) return DiffGroup(Res.string.backup_entry_nutzap_key, none, none, none) val before = change.before val after = change.after return when { - before != null && after == null -> DiffGroup(R.string.backup_entry_nutzap_key, listOf(ReviewItem.Text(before)), none, none) - before == null && after != null -> DiffGroup(R.string.backup_entry_nutzap_key, none, listOf(ReviewItem.Text(after)), none) - else -> DiffGroup(R.string.backup_entry_nutzap_key, none, none, listOf(ReviewItem.Text(arrow(before, after)))) + before != null && after == null -> DiffGroup(Res.string.backup_entry_nutzap_key, listOf(ReviewItem.Text(before)), none, none) + before == null && after != null -> DiffGroup(Res.string.backup_entry_nutzap_key, none, listOf(ReviewItem.Text(after)), none) + else -> DiffGroup(Res.string.backup_entry_nutzap_key, none, none, listOf(ReviewItem.Text(arrow(before, after)))) } } @@ -422,64 +536,64 @@ fun clip(text: String?): String { return if (oneLine.length > MAX_VALUE_LENGTH) oneLine.take(MAX_VALUE_LENGTH) + "…" else oneLine } -fun eventTypeName(type: BackupEventType): Int = +fun eventTypeName(type: BackupEventType): StringResource = when (type) { - BackupEventType.PROFILE -> R.string.backup_type_profile - BackupEventType.FOLLOW_LIST -> R.string.backup_type_follow_list - BackupEventType.MUTE_LIST -> R.string.backup_type_mute_list - BackupEventType.OUTBOX_INBOX_RELAYS -> R.string.backup_type_outbox_inbox_relays - BackupEventType.DM_RELAYS -> R.string.backup_type_dm_relays - BackupEventType.KEY_PACKAGE_RELAYS -> R.string.backup_type_key_package_relays - BackupEventType.SEARCH_RELAYS -> R.string.backup_type_search_relays - BackupEventType.INDEXER_RELAYS -> R.string.backup_type_indexer_relays - BackupEventType.RELAY_FEEDS -> R.string.backup_type_relay_feeds - BackupEventType.BLOCKED_RELAYS -> R.string.backup_type_blocked_relays - BackupEventType.TRUSTED_RELAYS -> R.string.backup_type_trusted_relays - BackupEventType.PRIVATE_OUTBOX_RELAYS -> R.string.backup_type_private_outbox_relays - BackupEventType.APP_SETTINGS -> R.string.backup_type_app_settings - BackupEventType.PUBLIC_CHATS -> R.string.backup_type_public_chats - BackupEventType.COMMUNITIES -> R.string.backup_type_communities - BackupEventType.HASHTAGS -> R.string.backup_type_hashtags - BackupEventType.GEOHASHES -> R.string.backup_type_geohashes - BackupEventType.FAVORITE_ALGO_FEEDS -> R.string.backup_type_favorite_algo_feeds - BackupEventType.EPHEMERAL_CHATS -> R.string.backup_type_ephemeral_chats - BackupEventType.RELAY_GROUPS -> R.string.backup_type_relay_groups - BackupEventType.CONCORD_COMMUNITIES -> R.string.backup_type_concord_communities - BackupEventType.TRUST_PROVIDERS -> R.string.backup_type_trust_providers - BackupEventType.CASHU_WALLET -> R.string.backup_type_cashu_wallet - BackupEventType.NUTZAP_INFO -> R.string.backup_type_nutzap_info - BackupEventType.PAYMENT_TARGETS -> R.string.backup_type_payment_targets - BackupEventType.BOLT12_OFFERS -> R.string.backup_type_bolt12_offers - BackupEventType.OTHER -> R.string.backup_type_other + BackupEventType.PROFILE -> Res.string.backup_type_profile + BackupEventType.FOLLOW_LIST -> Res.string.backup_type_follow_list + BackupEventType.MUTE_LIST -> Res.string.backup_type_mute_list + BackupEventType.OUTBOX_INBOX_RELAYS -> Res.string.backup_type_outbox_inbox_relays + BackupEventType.DM_RELAYS -> Res.string.backup_type_dm_relays + BackupEventType.KEY_PACKAGE_RELAYS -> Res.string.backup_type_key_package_relays + BackupEventType.SEARCH_RELAYS -> Res.string.backup_type_search_relays + BackupEventType.INDEXER_RELAYS -> Res.string.backup_type_indexer_relays + BackupEventType.RELAY_FEEDS -> Res.string.backup_type_relay_feeds + BackupEventType.BLOCKED_RELAYS -> Res.string.backup_type_blocked_relays + BackupEventType.TRUSTED_RELAYS -> Res.string.backup_type_trusted_relays + BackupEventType.PRIVATE_OUTBOX_RELAYS -> Res.string.backup_type_private_outbox_relays + BackupEventType.APP_SETTINGS -> Res.string.backup_type_app_settings + BackupEventType.PUBLIC_CHATS -> Res.string.backup_type_public_chats + BackupEventType.COMMUNITIES -> Res.string.backup_type_communities + BackupEventType.HASHTAGS -> Res.string.backup_type_hashtags + BackupEventType.GEOHASHES -> Res.string.backup_type_geohashes + BackupEventType.FAVORITE_ALGO_FEEDS -> Res.string.backup_type_favorite_algo_feeds + BackupEventType.EPHEMERAL_CHATS -> Res.string.backup_type_ephemeral_chats + BackupEventType.RELAY_GROUPS -> Res.string.backup_type_relay_groups + BackupEventType.CONCORD_COMMUNITIES -> Res.string.backup_type_concord_communities + BackupEventType.TRUST_PROVIDERS -> Res.string.backup_type_trust_providers + BackupEventType.CASHU_WALLET -> Res.string.backup_type_cashu_wallet + BackupEventType.NUTZAP_INFO -> Res.string.backup_type_nutzap_info + BackupEventType.PAYMENT_TARGETS -> Res.string.backup_type_payment_targets + BackupEventType.BOLT12_OFFERS -> Res.string.backup_type_bolt12_offers + BackupEventType.OTHER -> Res.string.backup_type_other } -fun eventTypeExplainer(type: BackupEventType): Int = +fun eventTypeExplainer(type: BackupEventType): StringResource = when (type) { - BackupEventType.PROFILE -> R.string.backup_type_profile_explainer - BackupEventType.FOLLOW_LIST -> R.string.backup_type_follow_list_explainer - BackupEventType.MUTE_LIST -> R.string.backup_type_mute_list_explainer - BackupEventType.OUTBOX_INBOX_RELAYS -> R.string.backup_type_outbox_inbox_relays_explainer - BackupEventType.DM_RELAYS -> R.string.backup_type_dm_relays_explainer - BackupEventType.KEY_PACKAGE_RELAYS -> R.string.backup_type_key_package_relays_explainer - BackupEventType.SEARCH_RELAYS -> R.string.backup_type_search_relays_explainer - BackupEventType.INDEXER_RELAYS -> R.string.backup_type_indexer_relays_explainer - BackupEventType.RELAY_FEEDS -> R.string.backup_type_relay_feeds_explainer - BackupEventType.BLOCKED_RELAYS -> R.string.backup_type_blocked_relays_explainer - BackupEventType.TRUSTED_RELAYS -> R.string.backup_type_trusted_relays_explainer - BackupEventType.PRIVATE_OUTBOX_RELAYS -> R.string.backup_type_private_outbox_relays_explainer - BackupEventType.APP_SETTINGS -> R.string.backup_type_app_settings_explainer - BackupEventType.PUBLIC_CHATS -> R.string.backup_type_public_chats_explainer - BackupEventType.COMMUNITIES -> R.string.backup_type_communities_explainer - BackupEventType.HASHTAGS -> R.string.backup_type_hashtags_explainer - BackupEventType.GEOHASHES -> R.string.backup_type_geohashes_explainer - BackupEventType.FAVORITE_ALGO_FEEDS -> R.string.backup_type_favorite_algo_feeds_explainer - BackupEventType.EPHEMERAL_CHATS -> R.string.backup_type_ephemeral_chats_explainer - BackupEventType.RELAY_GROUPS -> R.string.backup_type_relay_groups_explainer - BackupEventType.CONCORD_COMMUNITIES -> R.string.backup_type_concord_communities_explainer - BackupEventType.TRUST_PROVIDERS -> R.string.backup_type_trust_providers_explainer - BackupEventType.CASHU_WALLET -> R.string.backup_type_cashu_wallet_explainer - BackupEventType.NUTZAP_INFO -> R.string.backup_type_nutzap_info_explainer - BackupEventType.PAYMENT_TARGETS -> R.string.backup_type_payment_targets_explainer - BackupEventType.BOLT12_OFFERS -> R.string.backup_type_bolt12_offers_explainer - BackupEventType.OTHER -> R.string.backup_type_other_explainer + BackupEventType.PROFILE -> Res.string.backup_type_profile_explainer + BackupEventType.FOLLOW_LIST -> Res.string.backup_type_follow_list_explainer + BackupEventType.MUTE_LIST -> Res.string.backup_type_mute_list_explainer + BackupEventType.OUTBOX_INBOX_RELAYS -> Res.string.backup_type_outbox_inbox_relays_explainer + BackupEventType.DM_RELAYS -> Res.string.backup_type_dm_relays_explainer + BackupEventType.KEY_PACKAGE_RELAYS -> Res.string.backup_type_key_package_relays_explainer + BackupEventType.SEARCH_RELAYS -> Res.string.backup_type_search_relays_explainer + BackupEventType.INDEXER_RELAYS -> Res.string.backup_type_indexer_relays_explainer + BackupEventType.RELAY_FEEDS -> Res.string.backup_type_relay_feeds_explainer + BackupEventType.BLOCKED_RELAYS -> Res.string.backup_type_blocked_relays_explainer + BackupEventType.TRUSTED_RELAYS -> Res.string.backup_type_trusted_relays_explainer + BackupEventType.PRIVATE_OUTBOX_RELAYS -> Res.string.backup_type_private_outbox_relays_explainer + BackupEventType.APP_SETTINGS -> Res.string.backup_type_app_settings_explainer + BackupEventType.PUBLIC_CHATS -> Res.string.backup_type_public_chats_explainer + BackupEventType.COMMUNITIES -> Res.string.backup_type_communities_explainer + BackupEventType.HASHTAGS -> Res.string.backup_type_hashtags_explainer + BackupEventType.GEOHASHES -> Res.string.backup_type_geohashes_explainer + BackupEventType.FAVORITE_ALGO_FEEDS -> Res.string.backup_type_favorite_algo_feeds_explainer + BackupEventType.EPHEMERAL_CHATS -> Res.string.backup_type_ephemeral_chats_explainer + BackupEventType.RELAY_GROUPS -> Res.string.backup_type_relay_groups_explainer + BackupEventType.CONCORD_COMMUNITIES -> Res.string.backup_type_concord_communities_explainer + BackupEventType.TRUST_PROVIDERS -> Res.string.backup_type_trust_providers_explainer + BackupEventType.CASHU_WALLET -> Res.string.backup_type_cashu_wallet_explainer + BackupEventType.NUTZAP_INFO -> Res.string.backup_type_nutzap_info_explainer + BackupEventType.PAYMENT_TARGETS -> Res.string.backup_type_payment_targets_explainer + BackupEventType.BOLT12_OFFERS -> Res.string.backup_type_bolt12_offers_explainer + BackupEventType.OTHER -> Res.string.backup_type_other_explainer } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt index 8b5d7b0150..6497b54846 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt @@ -59,11 +59,34 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.routeFor +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.backup_action_block_again +import com.vitorpamplona.amethyst.commons.resources.backup_action_keep_count +import com.vitorpamplona.amethyst.commons.resources.backup_action_rejoin +import com.vitorpamplona.amethyst.commons.resources.backup_action_remute +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_count +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_feeds +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_places +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_profile +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_providers +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_relays +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_topics +import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_wallet +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_added +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_changed +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_keep_new +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_added +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_changed +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_cleared +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_removed +import com.vitorpamplona.amethyst.commons.resources.backup_conflict_restore_mine +import com.vitorpamplona.amethyst.commons.resources.backup_review_changed_by_other_app +import com.vitorpamplona.amethyst.commons.resources.backup_review_title_cleared +import com.vitorpamplona.amethyst.commons.resources.backup_review_title_updated import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton @@ -97,22 +120,23 @@ import com.vitorpamplona.quartz.nip60Cashu.wallet.CashuWalletDiff import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListDiff import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff +import org.jetbrains.compose.resources.StringResource /** A row of the review list, flattened so hundreds of entries scroll lazily. */ @Immutable internal sealed interface ReviewRow { class Section( - val titleRes: Int, + val titleRes: StringResource, val count: Int, val tone: Tone, ) : ReviewRow class Note( - val textRes: Int, + val textRes: StringResource, ) : ReviewRow class Group( - val labelRes: Int, + val labelRes: StringResource, ) : ReviewRow class Entry( @@ -124,15 +148,15 @@ internal enum class Tone { REMOVED, ADDED, CHANGED } internal fun buildRows( presentation: DiffPresentation, - skipGroups: Set = emptySet(), + skipGroups: Set = emptySet(), ): List { val rows = mutableListOf() fun section( - titleRes: Int, + titleRes: StringResource, count: Int, tone: Tone, - contentNote: Int?, + contentNote: StringResource?, items: (DiffGroup) -> List, ) { if (count == 0 && contentNote == null) return @@ -149,9 +173,9 @@ internal fun buildRows( } val content = presentation.content - section(R.string.backup_conflict_removed, presentation.removedCount, Tone.REMOVED, R.string.backup_conflict_private_cleared.takeIf { content == ContentChange.CLEARED }) { it.removed } - section(R.string.backup_conflict_added, presentation.addedCount, Tone.ADDED, R.string.backup_conflict_private_added.takeIf { content == ContentChange.ADDED }) { it.added } - section(R.string.backup_conflict_changed, presentation.changedCount, Tone.CHANGED, R.string.backup_conflict_private_changed.takeIf { content == ContentChange.CHANGED }) { it.changed } + section(Res.string.backup_conflict_removed, presentation.removedCount, Tone.REMOVED, Res.string.backup_conflict_private_cleared.takeIf { content == ContentChange.CLEARED }) { it.removed } + section(Res.string.backup_conflict_added, presentation.addedCount, Tone.ADDED, Res.string.backup_conflict_private_added.takeIf { content == ContentChange.ADDED }) { it.added } + section(Res.string.backup_conflict_changed, presentation.changedCount, Tone.CHANGED, Res.string.backup_conflict_private_changed.takeIf { content == ContentChange.CHANGED }) { it.changed } return rows } @@ -268,7 +292,7 @@ internal fun LazyListScope.genericDiffItems( } @Composable -internal fun GroupLabel(labelRes: Int) { +internal fun GroupLabel(labelRes: StringResource) { Text( text = stringRes(labelRes), style = MaterialTheme.typography.labelLarge, @@ -339,7 +363,7 @@ internal fun eventTitle(conflict: ReplaceableBackupConflict): String { val name = stringRes(eventTypeName(conflict.eventType)).replaceFirstChar { it.uppercase() } // Says what happened to it, not just what it is: "Community list updated". val emptied = conflict.incoming.tags.isEmpty() && conflict.incoming.content.isEmpty() - return stringRes(if (emptied) R.string.backup_review_title_cleared else R.string.backup_review_title_updated, name) + return stringRes(if (emptied) Res.string.backup_review_title_cleared else Res.string.backup_review_title_updated, name) } /** The two choices, worded for what they do to this event: "Keep 120" / "Restore 523". */ @@ -348,48 +372,48 @@ private fun actionLabels(conflict: ReplaceableBackupConflict): Pair { val (saved, new) = conflict.followCounts - stringRes(R.string.backup_action_keep_count, new.toString()) to stringRes(R.string.backup_action_restore_count, saved.toString()) + stringRes(Res.string.backup_action_keep_count, new.toString()) to stringRes(Res.string.backup_action_restore_count, saved.toString()) } is MuteListDiff -> { val removed = diff.publicMutes.removed.size - stringRes(R.string.backup_conflict_keep_new) to - if (removed > 0) stringRes(R.string.backup_action_remute, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine) + stringRes(Res.string.backup_conflict_keep_new) to + if (removed > 0) stringRes(Res.string.backup_action_remute, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine) } is SimpleGroupListDiff -> { val removed = diff.groups.removed.size - stringRes(R.string.backup_conflict_keep_new) to - if (removed > 0) stringRes(R.string.backup_action_rejoin, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine) + stringRes(Res.string.backup_conflict_keep_new) to + if (removed > 0) stringRes(Res.string.backup_action_rejoin, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine) } - is MetadataDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_profile) + is MetadataDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_profile) is RelayListDiff -> if (conflict.eventType == BackupEventType.BLOCKED_RELAYS && diff.relays.removed.isNotEmpty()) { - stringRes(R.string.backup_conflict_keep_new) to + stringRes(Res.string.backup_conflict_keep_new) to stringRes( - R.string.backup_action_block_again, + Res.string.backup_action_block_again, diff.relays.removed.size .toString(), ) } else { - stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays) + stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_relays) } - is AdvertisedRelayListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays) + is AdvertisedRelayListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_relays) is ChannelListDiff -> rejoinLabels(diff.channels.removed.size) is CommunityListDiff -> rejoinLabels(diff.communities.removed.size) is EphemeralChatListDiff -> rejoinLabels(diff.rooms.removed.size) - is FavoriteAlgoFeedsListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_feeds) - is HashtagListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_topics) + is FavoriteAlgoFeedsListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_feeds) + is HashtagListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_topics) // Geohashes are places, not topics: the screen counts "places gone" under "Followed // locations", so the button has to agree with the page it sits on. - is GeohashListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_places) - is TrustProviderListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_providers) - is CashuWalletDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_wallet) - else -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_conflict_restore_mine) + is GeohashListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_places) + is TrustProviderListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_providers) + is CashuWalletDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_wallet) + else -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_conflict_restore_mine) } @Composable private fun rejoinLabels(removed: Int): Pair = - stringRes(R.string.backup_conflict_keep_new) to - if (removed > 0) stringRes(R.string.backup_action_rejoin, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine) + stringRes(Res.string.backup_conflict_keep_new) to + if (removed > 0) stringRes(Res.string.backup_action_rejoin, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine) /** When the other app changed it, and what the event is for. */ @Composable @@ -399,7 +423,7 @@ private fun BackupConflictIntro( ) { Column(modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( - text = stringRes(R.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)), + text = stringRes(Res.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)), style = MaterialTheme.typography.labelLarge, color = conflictTones().removed, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt index 0e19b487cb..c892f3d020 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt @@ -42,12 +42,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.chats.ui.ThinSendButton import com.vitorpamplona.amethyst.commons.model.cordnGroups.CordnGroupChatroom import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggestionList +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_composer_hint import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord import com.vitorpamplona.amethyst.commons.ui.theme.EditFieldBorder @@ -233,7 +234,7 @@ internal fun CordnComposer( shape = EditFieldBorder, placeholder = { Text( - text = stringRes(R.string.cordn_composer_hint), + text = stringRes(Res.string.cordn_composer_hint), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateGroupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateGroupScreen.kt index 6deedde2d4..0b78b0ea5e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateGroupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateGroupScreen.kt @@ -55,11 +55,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig import com.vitorpamplona.amethyst.commons.cordn.DiscoveredCoordinator import com.vitorpamplona.amethyst.commons.cordn.GroupExposure @@ -69,6 +67,55 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.back +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_none +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen_on +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_unheard +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_hide_older +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_relays_more +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_all +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_fewer +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_older +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_stale_note +import com.vitorpamplona.amethyst.commons.resources.cordn_create_action +import com.vitorpamplona.amethyst.commons.resources.cordn_create_action_invite +import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin_count +import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin_only_me_on +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_change +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_discover +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_hide +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_new +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_pubkey +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_relays +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_all +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_partial +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_unknown +import com.vitorpamplona.amethyst.commons.resources.cordn_create_description +import com.vitorpamplona.amethyst.commons.resources.cordn_create_egalitarian +import com.vitorpamplona.amethyst.commons.resources.cordn_create_egalitarian_note +import com.vitorpamplona.amethyst.commons.resources.cordn_create_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_create_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_create_name +import com.vitorpamplona.amethyst.commons.resources.cordn_create_no_coordinator +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_link +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_no_welcome +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_open +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_skipped +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_title +import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_waiting +import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_none +import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_admins +import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_name +import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_people +import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_where +import com.vitorpamplona.amethyst.commons.resources.cordn_create_title +import com.vitorpamplona.amethyst.commons.resources.cordn_create_unreachable_count +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_member_count import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.model.cordn.CordnCoverage @@ -131,7 +178,7 @@ fun CordnCreateGroupScreen( Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back)) } }, - title = { Text(stringRes(R.string.cordn_create_title)) }, + title = { Text(stringRes(Res.string.cordn_create_title)) }, ) }, ) { padding -> @@ -139,8 +186,8 @@ fun CordnCreateGroupScreen( // The app's own empty state, centred and titled, rather than a // sentence stranded in the top-left corner. EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -164,14 +211,14 @@ fun CordnCreateGroupScreen( val draft = rememberCordnGroupDraft(accountViewModel) var creation by remember { mutableStateOf(null) } var error by remember { mutableStateOf(null) } - val failureFallback = stringRes(R.string.cordn_create_failed) + val failureFallback = stringRes(Res.string.cordn_create_failed) var busy by remember { mutableStateOf(false) } // Coordinators this account has never used, from their CEP-6 // announcements. Not added to the account by looking: picking one here // is what commits to it, and `createGroup` opens the session. var discovering by remember { mutableStateOf(false) } - val discoverFailed = stringRes(R.string.cordn_coordinators_discover_failed) + val discoverFailed = stringRes(Res.string.cordn_coordinators_discover_failed) // Offers this account already holds are not offers; they are the choices // above, and listing them twice would let the same coordinator be picked @@ -222,29 +269,29 @@ fun CordnCreateGroupScreen( verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - text = stringRes(R.string.cordn_create_explainer), + text = stringRes(Res.string.cordn_create_explainer), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) - StepHeading(1, stringRes(R.string.cordn_create_step_name)) + StepHeading(1, stringRes(Res.string.cordn_create_step_name)) OutlinedTextField( value = draft.name, onValueChange = { draft.name = it }, - label = { Text(stringRes(R.string.cordn_create_name)) }, + label = { Text(stringRes(Res.string.cordn_create_name)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) OutlinedTextField( value = draft.description, onValueChange = { draft.description = it }, - label = { Text(stringRes(R.string.cordn_create_description)) }, + label = { Text(stringRes(Res.string.cordn_create_description)) }, singleLine = false, modifier = Modifier.fillMaxWidth(), ) - StepHeading(2, stringRes(R.string.cordn_create_step_people)) + StepHeading(2, stringRes(Res.string.cordn_create_step_people)) RosterSummary( roster = draft.roster, @@ -254,7 +301,7 @@ fun CordnCreateGroupScreen( nav = nav, ) - StepHeading(3, stringRes(R.string.cordn_create_step_where)) + StepHeading(3, stringRes(Res.string.cordn_create_step_where)) // Collapsed to the one coordinator that will be used, because with a // draft.roster in hand there is usually nothing left to decide. The whole @@ -326,9 +373,9 @@ fun CordnCreateGroupScreen( TextButton(onClick = { draft.showAllLive = !draft.showAllLive }) { Text( if (draft.showAllLive) { - stringRes(R.string.cordn_coordinators_show_fewer) + stringRes(Res.string.cordn_coordinators_show_fewer) } else { - stringRes(R.string.cordn_coordinators_show_all, live.size) + stringRes(Res.string.cordn_coordinators_show_all, live.size) }, ) } @@ -338,9 +385,9 @@ fun CordnCreateGroupScreen( TextButton(onClick = { draft.showStale = !draft.showStale }) { Text( if (draft.showStale) { - stringRes(R.string.cordn_coordinators_hide_older) + stringRes(Res.string.cordn_coordinators_hide_older) } else { - stringRes(R.string.cordn_coordinators_show_older, stale.size) + stringRes(Res.string.cordn_coordinators_show_older, stale.size) }, ) } @@ -348,7 +395,7 @@ fun CordnCreateGroupScreen( if (draft.showStale && stale.isNotEmpty()) { Text( - text = stringRes(R.string.cordn_coordinators_stale_note), + text = stringRes(Res.string.cordn_coordinators_stale_note), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -372,7 +419,7 @@ fun CordnCreateGroupScreen( } CoordinatorChoice( - label = stringRes(R.string.cordn_create_coordinator_new), + label = stringRes(Res.string.cordn_create_coordinator_new), pubKey = null, selected = draft.selected == null, onSelect = { @@ -402,20 +449,20 @@ fun CordnCreateGroupScreen( }, enabled = !discovering && !busy, ) { - Text(stringRes(R.string.cordn_create_coordinator_discover)) + Text(stringRes(Res.string.cordn_create_coordinator_discover)) } draft.discovered?.takeIf { offers.isEmpty() }?.let { result -> Text( text = if (result.unreachable.isEmpty()) { - stringRes(R.string.cordn_coordinators_discover_none) + stringRes(Res.string.cordn_coordinators_discover_none) } else { // "Nobody is announcing" and "we were not told" are // different answers and only one of them is final. // Reporting the first for the second sends someone // off to paste a pubkey by hand over a timeout. - stringRes(R.string.cordn_coordinators_discover_unheard, result.unreachable.size) + stringRes(Res.string.cordn_coordinators_discover_unheard, result.unreachable.size) }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -433,7 +480,7 @@ fun CordnCreateGroupScreen( draft.userPicked = true error = null }, - label = { Text(stringRes(R.string.cordn_create_coordinator_pubkey)) }, + label = { Text(stringRes(Res.string.cordn_create_coordinator_pubkey)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) @@ -444,7 +491,7 @@ fun CordnCreateGroupScreen( draft.userPicked = true error = null }, - label = { Text(stringRes(R.string.cordn_create_coordinator_relays)) }, + label = { Text(stringRes(Res.string.cordn_create_coordinator_relays)) }, // A coordinator has no address beyond its pubkey (§8.5), so // the relays are how it is reached and more than one is // ordinary. One per line rather than comma-separated, @@ -456,7 +503,7 @@ fun CordnCreateGroupScreen( } } - StepHeading(4, stringRes(R.string.cordn_create_step_admins)) + StepHeading(4, stringRes(Res.string.cordn_create_step_admins)) // spec/01.md §5.3: leaving the admin list empty is not "set it up // later", it is choosing draft.egalitarian permanently -- so it is offered @@ -472,15 +519,15 @@ fun CordnCreateGroupScreen( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { Column(Modifier.weight(1f)) { - Text(stringRes(R.string.cordn_create_egalitarian), style = MaterialTheme.typography.bodyMedium) + Text(stringRes(Res.string.cordn_create_egalitarian), style = MaterialTheme.typography.bodyMedium) Text( text = if (draft.egalitarian) { - stringRes(R.string.cordn_create_egalitarian_note) + stringRes(Res.string.cordn_create_egalitarian_note) } else if (draft.coAdmins.isEmpty()) { - stringRes(R.string.cordn_create_admin_only_me_on) + stringRes(Res.string.cordn_create_admin_only_me_on) } else { - pluralStringRes(LocalContext.current, R.plurals.cordn_create_admin_count, draft.coAdmins.size + 1, draft.coAdmins.size + 1) + pluralStringRes(Res.plurals.cordn_create_admin_count, draft.coAdmins.size + 1, draft.coAdmins.size + 1) }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -561,9 +608,9 @@ fun CordnCreateGroupScreen( ) { Text( if (draft.roster.isEmpty()) { - stringRes(R.string.cordn_create_action) + stringRes(Res.string.cordn_create_action) } else { - pluralStringRes(LocalContext.current, R.plurals.cordn_create_action_invite, draft.roster.size, draft.roster.size) + pluralStringRes(Res.plurals.cordn_create_action_invite, draft.roster.size, draft.roster.size) }, ) } @@ -691,9 +738,9 @@ private fun resolvedName( @Composable private fun announcedLabel(announcedAt: Long): String = if (TimeUtils.now() - announcedAt > TimeUtils.ONE_MONTH) { - stringRes(R.string.cordn_coordinators_discover_seen_on, timeAgoNoDot(announcedAt).trim()) + stringRes(Res.string.cordn_coordinators_discover_seen_on, timeAgoNoDot(announcedAt).trim()) } else { - stringRes(R.string.cordn_coordinators_discover_seen, timeAgoNoDot(announcedAt).trim()) + stringRes(Res.string.cordn_coordinators_discover_seen, timeAgoNoDot(announcedAt).trim()) } /** The hosts it answers on, the first two and a count of the rest. */ @@ -705,7 +752,7 @@ private fun relayLabel(relays: List): String? { return if (hosts.size <= 2) { shown } else { - stringRes(R.string.cordn_coordinators_relays_more, shown, hosts.size - 2) + stringRes(Res.string.cordn_coordinators_relays_more, shown, hosts.size - 2) } } @@ -791,7 +838,7 @@ private fun RosterSummary( ) { if (roster.isEmpty()) { Text( - text = stringRes(R.string.cordn_create_people_none), + text = stringRes(Res.string.cordn_create_people_none), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1f), @@ -804,7 +851,7 @@ private fun RosterSummary( } Column(Modifier.weight(1f)) { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, roster.size + 1, roster.size + 1), + text = pluralStringRes(Res.plurals.cordn_member_count, roster.size + 1, roster.size + 1), style = MaterialTheme.typography.bodyMedium, ) // Counted over coordinators that answered, so an unreachable one @@ -812,7 +859,7 @@ private fun RosterSummary( val unreachable = roster.count { member -> coverage.values.none { it.answered && member in it.reachable } } if (unreachable > 0 && coverage.values.any { it.answered }) { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_create_unreachable_count, unreachable, unreachable), + text = pluralStringRes(Res.plurals.cordn_create_unreachable_count, unreachable, unreachable), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, ) @@ -855,7 +902,7 @@ private fun CoordinatorSummary( ) { if (config == null) { Text( - text = stringRes(R.string.cordn_create_no_coordinator), + text = stringRes(Res.string.cordn_create_no_coordinator), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1f), @@ -879,9 +926,9 @@ private fun CoordinatorSummary( Text( text = when { - coverage == null || !coverage.answered -> stringRes(R.string.cordn_create_coverage_unknown) - reached == rosterSize -> stringRes(R.string.cordn_create_coverage_all, rosterSize) - else -> stringRes(R.string.cordn_create_coverage_partial, reached ?: 0, rosterSize) + coverage == null || !coverage.answered -> stringRes(Res.string.cordn_create_coverage_unknown) + reached == rosterSize -> stringRes(Res.string.cordn_create_coverage_all, rosterSize) + else -> stringRes(Res.string.cordn_create_coverage_partial, reached ?: 0, rosterSize) }, style = MaterialTheme.typography.labelSmall, color = @@ -899,9 +946,9 @@ private fun CoordinatorSummary( TextButton(onClick = onToggle) { Text( if (expanded) { - stringRes(R.string.cordn_create_coordinator_hide) + stringRes(Res.string.cordn_create_coordinator_hide) } else { - stringRes(R.string.cordn_create_coordinator_change) + stringRes(Res.string.cordn_create_coordinator_change) }, ) } @@ -934,11 +981,11 @@ private fun CordnCreationOutcome( AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringRes(R.string.cordn_create_outcome_title)) }, + title = { Text(stringRes(Res.string.cordn_create_outcome_title)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { Text( - text = stringRes(R.string.cordn_create_outcome_explainer), + text = stringRes(Res.string.cordn_create_outcome_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -960,10 +1007,10 @@ private fun CordnCreationOutcome( Text( text = when { - outcome == null -> stringRes(R.string.cordn_create_outcome_skipped) - outcome.sent -> stringRes(R.string.cordn_create_outcome_waiting) - outcome.joinedWithoutWelcome -> stringRes(R.string.cordn_create_outcome_no_welcome) - else -> outcome.failure?.message ?: stringRes(R.string.cordn_create_outcome_failed) + outcome == null -> stringRes(Res.string.cordn_create_outcome_skipped) + outcome.sent -> stringRes(Res.string.cordn_create_outcome_waiting) + outcome.joinedWithoutWelcome -> stringRes(Res.string.cordn_create_outcome_no_welcome) + else -> outcome.failure?.message ?: stringRes(Res.string.cordn_create_outcome_failed) }, style = MaterialTheme.typography.labelSmall, color = @@ -979,12 +1026,12 @@ private fun CordnCreationOutcome( // open themselves -- which needs the group to exist, // and now it does. if (outcome == null) { - TextButton(onClick = open) { Text(stringRes(R.string.cordn_create_outcome_link)) } + TextButton(onClick = open) { Text(stringRes(Res.string.cordn_create_outcome_link)) } } } } } }, - confirmButton = { TextButton(onClick = open) { Text(stringRes(R.string.cordn_create_outcome_open)) } }, + confirmButton = { TextButton(onClick = open) { Text(stringRes(Res.string.cordn_create_outcome_open)) } }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateMembersScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateMembersScreen.kt index e80829bf90..1a09a1c890 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateMembersScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnCreateMembersScreen.kt @@ -47,15 +47,31 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.User +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_create_add_member +import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin +import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_done +import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_empty +import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_note +import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_count +import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_none +import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_unknown +import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_people +import com.vitorpamplona.amethyst.commons.resources.cordn_create_you +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_none +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_placeholder +import com.vitorpamplona.amethyst.commons.resources.cordn_info_has_key_package +import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member +import com.vitorpamplona.amethyst.commons.resources.cordn_member_count import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton @@ -101,12 +117,12 @@ fun CordnCreateMembersScreen( val draft = rememberCordnGroupDraft(accountViewModel) Scaffold( - topBar = { TopBarWithBackButton(stringRes(R.string.cordn_create_step_people), nav) }, + topBar = { TopBarWithBackButton(stringRes(Res.string.cordn_create_step_people), nav) }, ) { padding -> if (runtime == null) { EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -137,7 +153,7 @@ fun CordnCreateMembersScreen( verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - text = stringRes(R.string.cordn_create_people_note), + text = stringRes(Res.string.cordn_create_people_note), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -148,8 +164,8 @@ fun CordnCreateMembersScreen( search = it if (it.length > 2) userSuggestions.processCurrentWord(it) else userSuggestions.reset() }, - label = { Text(stringRes(R.string.cordn_create_add_member)) }, - placeholder = { Text(stringRes(R.string.cordn_info_add_member_placeholder)) }, + label = { Text(stringRes(Res.string.cordn_create_add_member)) }, + placeholder = { Text(stringRes(Res.string.cordn_info_add_member_placeholder)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) @@ -168,7 +184,7 @@ fun CordnCreateMembersScreen( modifier = SuggestionListDefaultHeightChat, onEmpty = { Text( - text = stringRes(R.string.cordn_info_add_member_none), + text = stringRes(Res.string.cordn_info_add_member_none), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), @@ -182,7 +198,7 @@ fun CordnCreateMembersScreen( if (user.pubkeyHex in reachable) { Icon( symbol = MaterialSymbols.Key, - contentDescription = stringRes(R.string.cordn_info_has_key_package), + contentDescription = stringRes(Res.string.cordn_info_has_key_package), modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -194,7 +210,7 @@ fun CordnCreateMembersScreen( }) { Icon( symbol = MaterialSymbols.PersonAdd, - contentDescription = stringRes(R.string.cordn_create_add_member), + contentDescription = stringRes(Res.string.cordn_create_add_member), tint = MaterialTheme.colorScheme.primary, ) } @@ -206,7 +222,7 @@ fun CordnCreateMembersScreen( HorizontalDivider() Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, draft.roster.size + 1, draft.roster.size + 1), + text = pluralStringRes(Res.plurals.cordn_member_count, draft.roster.size + 1, draft.roster.size + 1), style = MaterialTheme.typography.titleSmall, ) @@ -230,7 +246,7 @@ fun CordnCreateMembersScreen( if (draft.roster.isEmpty()) { Text( - text = stringRes(R.string.cordn_create_people_empty), + text = stringRes(Res.string.cordn_create_people_empty), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -240,7 +256,7 @@ fun CordnCreateMembersScreen( onClick = { nav.popBack() }, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), ) { - Text(stringRes(R.string.cordn_create_people_done)) + Text(stringRes(Res.string.cordn_create_people_done)) } } } @@ -263,14 +279,14 @@ private fun CreatorRow( Column(Modifier.weight(1f)) { Text(observeUserNameByHex(me, accountViewModel), style = MaterialTheme.typography.bodyMedium) Text( - text = stringRes(R.string.cordn_create_you), + text = stringRes(Res.string.cordn_create_you), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } if (!egalitarian) { Text( - text = stringRes(R.string.cordn_create_admin), + text = stringRes(Res.string.cordn_create_admin), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary, ) @@ -304,9 +320,9 @@ private fun MemberRow( Text( text = when { - answered == 0 -> stringRes(R.string.cordn_create_reach_unknown) - reaching > 0 -> pluralStringRes(LocalContext.current, R.plurals.cordn_create_reach_count, reaching, reaching) - else -> stringRes(R.string.cordn_create_reach_none) + answered == 0 -> stringRes(Res.string.cordn_create_reach_unknown) + reaching > 0 -> pluralStringRes(Res.plurals.cordn_create_reach_count, reaching, reaching) + else -> stringRes(Res.string.cordn_create_reach_none) }, style = MaterialTheme.typography.labelSmall, color = @@ -323,7 +339,7 @@ private fun MemberRow( if (!egalitarian) { TextButton(onClick = onToggleAdmin) { Text( - text = stringRes(R.string.cordn_create_admin), + text = stringRes(Res.string.cordn_create_admin), style = MaterialTheme.typography.labelMedium, color = if (isAdmin) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -333,7 +349,7 @@ private fun MemberRow( IconButton(onClick = onRemove) { Icon( symbol = MaterialSymbols.PersonRemove, - contentDescription = stringRes(R.string.cordn_info_remove_member), + contentDescription = stringRes(Res.string.cordn_info_remove_member), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index 7e0e2d7a13..b884f56dbd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -69,7 +69,6 @@ import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CordnGroupManager import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -78,10 +77,33 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.back import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.cordn_action_editing +import com.vitorpamplona.amethyst.commons.resources.cordn_action_unpin +import com.vitorpamplona.amethyst.commons.resources.cordn_chat_empty_description +import com.vitorpamplona.amethyst.commons.resources.cordn_chat_empty_title +import com.vitorpamplona.amethyst.commons.resources.cordn_group_info +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled +import com.vitorpamplona.amethyst.commons.resources.cordn_media_download_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_media_no_server +import com.vitorpamplona.amethyst.commons.resources.cordn_media_unreadable +import com.vitorpamplona.amethyst.commons.resources.cordn_media_upload_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_member_count +import com.vitorpamplona.amethyst.commons.resources.cordn_message_deleted +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_by +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_count +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_next +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_previous +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_show_all +import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_title +import com.vitorpamplona.amethyst.commons.resources.cordn_send_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_send_no_session +import com.vitorpamplona.amethyst.commons.resources.cordn_voice_record +import com.vitorpamplona.amethyst.commons.resources.cordn_voice_stop import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo import com.vitorpamplona.amethyst.commons.ui.components.EmptyState +import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText @@ -152,7 +174,7 @@ fun CordnGroupChatScreen( // (it was forgotten, or never opened) and not an error to throw at the // user as a blank screen. Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.Center) { - Text(stringRes(R.string.cordn_group_unavailable), style = MaterialTheme.typography.bodyLarge) + Text(stringRes(Res.string.cordn_group_unavailable), style = MaterialTheme.typography.bodyLarge) } return } @@ -209,9 +231,9 @@ private fun CordnGroupChat( // and simply never arrived. var sendError by remember { mutableStateOf(null) } val context = LocalContext.current - val uploadFailed = stringRes(R.string.cordn_media_upload_failed) - val sendFailed = stringRes(R.string.cordn_send_failed) - val noSession = stringRes(R.string.cordn_send_no_session) + val uploadFailed = stringRes(Res.string.cordn_media_upload_failed) + val sendFailed = stringRes(Res.string.cordn_send_failed) + val noSession = stringRes(Res.string.cordn_send_no_session) fun manager() = accountViewModel.account.cordnRuntime @@ -359,8 +381,8 @@ private fun CordnGroupChat( // action that actually needed it. if (rows.isEmpty()) { EmptyState( - title = stringRes(R.string.cordn_chat_empty_title), - description = stringRes(R.string.cordn_chat_empty_description), + title = stringRes(Res.string.cordn_chat_empty_title), + description = stringRes(Res.string.cordn_chat_empty_description), modifier = Modifier.weight(1f), ) } @@ -548,7 +570,7 @@ private fun CordnGroupChat( } if (editing != null) { ComposerBanner( - label = stringRes(R.string.cordn_action_editing), + label = stringRes(Res.string.cordn_action_editing), onCancel = { editing = null room.draft.value = "" @@ -712,7 +734,7 @@ private fun PinnedRibbon( IconButton(onClick = { index = (at - 1 + pinned.size) % pinned.size }, modifier = Modifier.size(28.dp)) { Icon( symbol = MaterialSymbols.AutoMirrored.KeyboardArrowLeft, - contentDescription = stringRes(R.string.cordn_pinned_previous), + contentDescription = stringRes(Res.string.cordn_pinned_previous), modifier = Modifier.size(18.dp), ) } @@ -724,7 +746,7 @@ private fun PinnedRibbon( IconButton(onClick = { index = (at + 1) % pinned.size }, modifier = Modifier.size(28.dp)) { Icon( symbol = MaterialSymbols.AutoMirrored.KeyboardArrowRight, - contentDescription = stringRes(R.string.cordn_pinned_next), + contentDescription = stringRes(Res.string.cordn_pinned_next), modifier = Modifier.size(18.dp), ) } @@ -733,7 +755,7 @@ private fun PinnedRibbon( IconButton(onClick = { showingAll = true }, modifier = Modifier.size(28.dp)) { Icon( symbol = MaterialSymbols.AutoMirrored.List, - contentDescription = stringRes(R.string.cordn_pinned_show_all), + contentDescription = stringRes(Res.string.cordn_pinned_show_all), modifier = Modifier.size(18.dp), ) } @@ -786,7 +808,7 @@ private fun PinnedLine( // points at. text = if (annotations.isDeleted(message.envelope.id)) { - stringRes(R.string.cordn_message_deleted) + stringRes(Res.string.cordn_message_deleted) } else { annotations.contentOf(message.envelope.id).orEmpty() }, @@ -822,7 +844,7 @@ private fun AllPinnedDialog( Row(verticalAlignment = Alignment.CenterVertically) { Icon(MaterialSymbols.PushPin, contentDescription = null, modifier = Modifier.size(18.dp)) Text( - text = stringRes(R.string.cordn_pinned_title), + text = stringRes(Res.string.cordn_pinned_title), modifier = Modifier.padding(start = 8.dp), ) } @@ -830,7 +852,7 @@ private fun AllPinnedDialog( text = { Column { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_pinned_count, pinned.size, pinned.size), + text = pluralStringRes(Res.plurals.cordn_pinned_count, pinned.size, pinned.size), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -861,7 +883,7 @@ private fun AllPinnedDialog( modifier = Modifier.weight(1f).padding(start = 8.dp), ) TextButton(onClick = { onUnpin(message) }) { - Text(stringRes(R.string.cordn_action_unpin), style = MaterialTheme.typography.labelSmall) + Text(stringRes(Res.string.cordn_action_unpin), style = MaterialTheme.typography.labelSmall) } } @@ -869,7 +891,7 @@ private fun AllPinnedDialog( Text( text = stringRes( - R.string.cordn_pinned_by, + Res.string.cordn_pinned_by, observeUserNameByHex(pinnedBy, accountViewModel), ), style = MaterialTheme.typography.labelSmall, @@ -880,7 +902,7 @@ private fun AllPinnedDialog( Text( text = if (annotations.isDeleted(message.envelope.id)) { - stringRes(R.string.cordn_message_deleted) + stringRes(Res.string.cordn_message_deleted) } else { annotations.contentOf(message.envelope.id).orEmpty() }, @@ -944,7 +966,7 @@ private fun CordnChatTopBar( Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis) if (members.isNotEmpty()) { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, members.size, members.size), + text = pluralStringRes(Res.plurals.cordn_member_count, members.size, members.size), style = MaterialTheme.typography.bodySmall, ) } @@ -963,7 +985,7 @@ private fun CordnChatTopBar( // GroupAdd rather than Info, as in Marmot: the same screen, and the thing // people come to it for is adding someone. IconButton(onClick = onInfo) { - Icon(MaterialSymbols.GroupAdd, contentDescription = stringRes(R.string.cordn_group_info)) + Icon(MaterialSymbols.GroupAdd, contentDescription = stringRes(Res.string.cordn_group_info)) } }, ) @@ -995,10 +1017,10 @@ private suspend fun sendAttachment( // a reason a person can act on. val session = accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session)) val group = session.manager.group(room.gid) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session)) // The gallery inside the dialog can delete what was picked, which leaves an empty // orchestrator rather than a null one. canPost() now refuses that, but indexing it @@ -1052,14 +1074,14 @@ private suspend fun sendAttachment( val name = withContext(Dispatchers.IO) { resolveDisplayName(context, uri) } val bytes = withContext(Dispatchers.IO) { context.contentResolver.openInputStream(finalUri)?.use { it.readBytes() } } - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_unreadable)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_unreadable)) // Null means the chosen host has no base URL, which is a setting the person can // change — the one failure here that is entirely actionable. val tag = CordnMediaService(accountViewModel.account) .upload(group, bytes, mime, name, context, state.selectedServer.baseUrl) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_no_server)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_no_server)) // Into the room as well, for the same reason every other send is: an // attachment of your own echoes back as an Echo and would otherwise be @@ -1140,7 +1162,7 @@ internal fun CordnAttachment( if (mediaKey == null) { Text( - text = stringRes(R.string.cordn_media_download_failed), + text = stringRes(Res.string.cordn_media_download_failed), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, ) @@ -1254,7 +1276,7 @@ internal fun VoiceNoteButton( ) { Icon( symbol = if (recording) MaterialSymbols.Stop else MaterialSymbols.Mic, - contentDescription = stringRes(if (recording) R.string.cordn_voice_stop else R.string.cordn_voice_record), + contentDescription = stringRes(if (recording) Res.string.cordn_voice_stop else Res.string.cordn_voice_record), // Matches the attach icon beside it; red only while recording, // which is the one state worth pulling the eye. tint = if (recording) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.placeholderText, @@ -1284,10 +1306,10 @@ private suspend fun sendVoiceNote( // note that goes nowhere and says nothing is the same bug twice. val session = accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session)) val group = session.manager.group(room.gid) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session)) try { val bytes = withContext(Dispatchers.IO) { recording.file.readBytes() } @@ -1304,7 +1326,7 @@ private suspend fun sendVoiceNote( // is what makes the bubble match the preview. waveform = recording.amplitudes, ) - ?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_no_server)) + ?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_no_server)) // Into the room as well, for the same reason every other send is: an // attachment of your own echoes back as an Echo and would otherwise be // invisible to the person who sent it. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt index 25c90c354e..b08e44b341 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup -import android.content.Context import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable @@ -58,12 +57,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CordnGroupException import com.vitorpamplona.amethyst.commons.cordn.GroupExposure import com.vitorpamplona.amethyst.commons.cordn.ui.CordnExposureCard @@ -76,8 +73,58 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.back import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.copy_npub_to_clipboard +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_action_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_coordinator_silent +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_key_package +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_self_remove +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_welcome +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_not_a_member +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_someone +import com.vitorpamplona.amethyst.commons.resources.cordn_admin_wrong_key_package +import com.vitorpamplona.amethyst.commons.resources.cordn_create_description +import com.vitorpamplona.amethyst.commons.resources.cordn_create_name +import com.vitorpamplona.amethyst.commons.resources.cordn_exposure_close +import com.vitorpamplona.amethyst.commons.resources.cordn_exposure_open +import com.vitorpamplona.amethyst.commons.resources.cordn_group_info +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_none +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_note +import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_placeholder +import com.vitorpamplona.amethyst.commons.resources.cordn_info_admin_badge +import com.vitorpamplona.amethyst.commons.resources.cordn_info_admins_kept +import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator +import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator_key +import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator_nprofile +import com.vitorpamplona.amethyst.commons.resources.cordn_info_copy_nprofile +import com.vitorpamplona.amethyst.commons.resources.cordn_info_edit_details +import com.vitorpamplona.amethyst.commons.resources.cordn_info_egalitarian +import com.vitorpamplona.amethyst.commons.resources.cordn_info_epoch +import com.vitorpamplona.amethyst.commons.resources.cordn_info_gid +import com.vitorpamplona.amethyst.commons.resources.cordn_info_has_key_package +import com.vitorpamplona.amethyst.commons.resources.cordn_info_members +import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_confirm_body +import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_confirm_title +import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member +import com.vitorpamplona.amethyst.commons.resources.cordn_info_save +import com.vitorpamplona.amethyst.commons.resources.cordn_info_technical +import com.vitorpamplona.amethyst.commons.resources.cordn_member_count +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_accept +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_admin_only +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_check +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_decline +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_none +import com.vitorpamplona.amethyst.commons.resources.cordn_requests_title +import com.vitorpamplona.amethyst.commons.resources.cordn_send_no_session +import com.vitorpamplona.amethyst.commons.resources.cordn_share_copied +import com.vitorpamplona.amethyst.commons.resources.cordn_share_copy +import com.vitorpamplona.amethyst.commons.resources.cordn_share_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_share_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState +import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightChat import com.vitorpamplona.amethyst.commons.util.toShortDisplay @@ -124,8 +171,8 @@ fun CordnGroupInfoScreen( // The app's own empty state, centred and titled, rather than a // sentence stranded in the top-left corner. EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) } @@ -151,7 +198,7 @@ private fun InfoTopBar( Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back)) } }, - title = { Text(stringRes(R.string.cordn_group_info)) }, + title = { Text(stringRes(Res.string.cordn_group_info)) }, actions = actions, ) } @@ -208,8 +255,8 @@ private fun CordnGroupInfo( DisposableEffect(Unit) { onDispose { userSuggestions.reset() } } - val failed = stringRes(R.string.cordn_admin_action_failed) - val noSession = stringRes(R.string.cordn_send_no_session) + val failed = stringRes(Res.string.cordn_admin_action_failed) + val noSession = stringRes(Res.string.cordn_send_no_session) // Computed, never asserted: hardcoding these made the card look like a // disclosure while reporting the same four values for every group. @@ -236,7 +283,6 @@ private fun CordnGroupInfo( * roster, and a rename stayed the old name. The runtime's own methods pair * each commit with that refresh. */ - val context = LocalContext.current fun runAdmin( who: String? = null, @@ -257,7 +303,7 @@ private fun CordnGroupInfo( // and gids in full, so the screen that had just shown a face // answered with 64 hex characters. Log.w("CordnGroupInfo", "admin action failed in ${room.gid}: ${e.message}", e) - adminError = adminFailureText(context, e, who, failed) + adminError = adminFailureText(e, who, failed) } finally { busy = false } @@ -274,7 +320,7 @@ private fun CordnGroupInfo( IconButton(onClick = { renaming = true }, enabled = !busy) { Icon( symbol = MaterialSymbols.Edit, - contentDescription = stringRes(R.string.cordn_info_edit_details), + contentDescription = stringRes(Res.string.cordn_info_edit_details), ) } } @@ -303,7 +349,7 @@ private fun CordnGroupInfo( } // A subtitle, not a labelled row above a list that is itself the count. Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, members.size, members.size), + text = pluralStringRes(Res.plurals.cordn_member_count, members.size, members.size), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(top = 4.dp), @@ -321,7 +367,7 @@ private fun CordnGroupInfo( // ContextVM addresses it with ordinary p-tags, so it has a kind 0 like // anyone else and the app can already resolve it to a name, an avatar // and a profile to tap through to. - Text(stringRes(R.string.cordn_info_coordinator), style = MaterialTheme.typography.titleMedium) + Text(stringRes(Res.string.cordn_info_coordinator), style = MaterialTheme.typography.titleMedium) Row( Modifier.fillMaxWidth().padding(top = 8.dp), @@ -347,7 +393,7 @@ private fun CordnGroupInfo( IconButton(onClick = { showExposure = true }, enabled = exposure != null) { Icon( symbol = MaterialSymbols.Info, - contentDescription = stringRes(R.string.cordn_exposure_open), + contentDescription = stringRes(Res.string.cordn_exposure_open), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } @@ -355,7 +401,7 @@ private fun CordnGroupInfo( HorizontalDivider(Modifier.padding(vertical = 16.dp)) - Text(stringRes(R.string.cordn_info_members), style = MaterialTheme.typography.titleMedium) + Text(stringRes(Res.string.cordn_info_members), style = MaterialTheme.typography.titleMedium) // Said once, and only when it is true. An empty admin set is not "none // configured" -- spec/01.md makes it permanently egalitarian, so the @@ -364,7 +410,7 @@ private fun CordnGroupInfo( // a count of them adds nothing. if (admins.isEmpty()) { Text( - text = stringRes(R.string.cordn_info_egalitarian), + text = stringRes(Res.string.cordn_info_egalitarian), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(top = 2.dp, bottom = 4.dp), @@ -385,7 +431,7 @@ private fun CordnGroupInfo( ) if (member in admins) { Text( - text = stringRes(R.string.cordn_info_admin_badge), + text = stringRes(Res.string.cordn_info_admin_badge), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary, ) @@ -398,7 +444,7 @@ private fun CordnGroupInfo( IconButton(onClick = { removing = member }, enabled = !busy) { Icon( symbol = MaterialSymbols.PersonRemove, - contentDescription = stringRes(R.string.cordn_info_remove_member), + contentDescription = stringRes(Res.string.cordn_info_remove_member), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.error, ) @@ -476,9 +522,9 @@ private fun CordnGroupInfo( // would be a setting nobody should be nudged into. AlertDialog( onDismissRequest = { removing = null }, - title = { Text(stringRes(R.string.cordn_info_remove_confirm_title)) }, + title = { Text(stringRes(Res.string.cordn_info_remove_confirm_title)) }, text = { - Text(stringRes(R.string.cordn_info_remove_confirm_body, removingName)) + Text(stringRes(Res.string.cordn_info_remove_confirm_body, removingName)) }, confirmButton = { TextButton(onClick = { @@ -486,7 +532,7 @@ private fun CordnGroupInfo( runAdmin(removingName) { it.removeMember(coordinatorPubKey, room.gid, target) } }) { Text( - text = stringRes(R.string.cordn_info_remove_member), + text = stringRes(Res.string.cordn_info_remove_member), color = MaterialTheme.colorScheme.error, ) } @@ -518,7 +564,7 @@ private fun CordnGroupInfo( text = { CordnExposureCard(it) }, confirmButton = { TextButton(onClick = { showExposure = false }) { - Text(stringRes(R.string.cordn_exposure_close)) + Text(stringRes(Res.string.cordn_exposure_close)) } }, ) @@ -577,15 +623,15 @@ private fun CordnAddMember( OutlinedTextField( value = search, onValueChange = onSearchChange, - label = { Text(stringRes(R.string.cordn_info_add_member)) }, - placeholder = { Text(stringRes(R.string.cordn_info_add_member_placeholder)) }, + label = { Text(stringRes(Res.string.cordn_info_add_member)) }, + placeholder = { Text(stringRes(Res.string.cordn_info_add_member_placeholder)) }, singleLine = true, enabled = !busy, modifier = Modifier.fillMaxWidth(), ) Text( - text = stringRes(R.string.cordn_info_add_member_note), + text = stringRes(Res.string.cordn_info_add_member_note), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(top = 4.dp), @@ -601,7 +647,7 @@ private fun CordnAddMember( modifier = SuggestionListDefaultHeightChat, onEmpty = { Text( - text = stringRes(R.string.cordn_info_add_member_none), + text = stringRes(Res.string.cordn_info_add_member_none), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), @@ -612,7 +658,7 @@ private fun CordnAddMember( if (reachable?.contains(user.pubkeyHex) == true) { Icon( symbol = MaterialSymbols.Key, - contentDescription = stringRes(R.string.cordn_info_has_key_package), + contentDescription = stringRes(Res.string.cordn_info_has_key_package), modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -620,7 +666,7 @@ private fun CordnAddMember( IconButton(onClick = { onInvite(user) }) { Icon( symbol = MaterialSymbols.PersonAdd, - contentDescription = stringRes(R.string.cordn_info_add_member), + contentDescription = stringRes(Res.string.cordn_info_add_member), tint = MaterialTheme.colorScheme.primary, ) } @@ -643,20 +689,19 @@ private fun CordnAddMember( * better than silence — and the caller has logged the throwable in full either * way. */ -private fun adminFailureText( - context: Context, +private suspend fun adminFailureText( e: Throwable, who: String?, fallback: String, ): String { - val name = who ?: stringRes(context, R.string.cordn_admin_someone) + val name = who ?: loadStringRes(Res.string.cordn_admin_someone) return when (e) { is CordnGroupException -> when (e.reason) { - CordnGroupException.Reason.NO_KEY_PACKAGE -> stringRes(context, R.string.cordn_admin_no_key_package, name) - CordnGroupException.Reason.WRONG_KEY_PACKAGE_OWNER -> stringRes(context, R.string.cordn_admin_wrong_key_package, name) - CordnGroupException.Reason.NO_WELCOME -> stringRes(context, R.string.cordn_admin_no_welcome, name) - CordnGroupException.Reason.NOT_A_MEMBER -> stringRes(context, R.string.cordn_admin_not_a_member, name) + CordnGroupException.Reason.NO_KEY_PACKAGE -> loadStringRes(Res.string.cordn_admin_no_key_package, name) + CordnGroupException.Reason.WRONG_KEY_PACKAGE_OWNER -> loadStringRes(Res.string.cordn_admin_wrong_key_package, name) + CordnGroupException.Reason.NO_WELCOME -> loadStringRes(Res.string.cordn_admin_no_welcome, name) + CordnGroupException.Reason.NOT_A_MEMBER -> loadStringRes(Res.string.cordn_admin_not_a_member, name) CordnGroupException.Reason.OTHER -> e.message ?: fallback } @@ -664,14 +709,14 @@ private fun adminFailureText( // roster and the button disagreed rather than that anyone tried. is IllegalArgumentException -> if (e.message?.contains("self-removal") == true) { - stringRes(context, R.string.cordn_admin_no_self_remove) + loadStringRes(Res.string.cordn_admin_no_self_remove) } else { e.message ?: fallback } // A coordinator that does not answer is the single commonest failure // here and says nothing useful in its own words. - is TimeoutCancellationException -> stringRes(context, R.string.cordn_admin_coordinator_silent) + is TimeoutCancellationException -> loadStringRes(Res.string.cordn_admin_coordinator_silent) else -> e.message ?: fallback } @@ -701,7 +746,7 @@ private fun TechnicalDetails( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringRes(R.string.cordn_info_technical), + text = stringRes(Res.string.cordn_info_technical), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f), ) @@ -725,22 +770,22 @@ private fun TechnicalDetails( val coordinator = remember(coordinatorPubKey) { LocalCache.getOrCreateUser(coordinatorPubKey) } CopyableKeyRow( - label = stringRes(R.string.cordn_info_coordinator_key), + label = stringRes(Res.string.cordn_info_coordinator_key), shown = coordinator.pubkeyDisplayHex(), copied = coordinator.pubkeyNpub(), copyDescription = stringRes(Res.string.copy_npub_to_clipboard), ) CopyableKeyRow( - label = stringRes(R.string.cordn_info_coordinator_nprofile), + label = stringRes(Res.string.cordn_info_coordinator_nprofile), shown = coordinator.toNProfile().toShortDisplay(6), copied = coordinator.toNProfile(), - copyDescription = stringRes(R.string.cordn_info_copy_nprofile), + copyDescription = stringRes(Res.string.cordn_info_copy_nprofile), ) SelectionContainer { Column { - InfoRow(stringRes(R.string.cordn_info_gid), gid) - InfoRow(stringRes(R.string.cordn_info_epoch), epoch.toString()) + InfoRow(stringRes(Res.string.cordn_info_gid), gid) + InfoRow(stringRes(Res.string.cordn_info_epoch), epoch.toString()) } } } @@ -835,7 +880,7 @@ private fun JoinRequests( ) { val runtime = accountViewModel.account.cordnRuntime ?: return val scope = rememberCoroutineScope() - val failed = stringRes(R.string.cordn_requests_failed) + val failed = stringRes(Res.string.cordn_requests_failed) var requests by remember { mutableStateOf?>(null) } var busy by remember { mutableStateOf(false) } @@ -853,15 +898,15 @@ private fun JoinRequests( } } - Text(stringRes(R.string.cordn_requests_title), style = MaterialTheme.typography.titleMedium) + Text(stringRes(Res.string.cordn_requests_title), style = MaterialTheme.typography.titleMedium) Text( - text = stringRes(R.string.cordn_requests_admin_only), + text = stringRes(Res.string.cordn_requests_admin_only), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) OutlinedButton(onClick = { scope.launch { reload() } }, enabled = !busy) { - Text(stringRes(R.string.cordn_requests_check)) + Text(stringRes(Res.string.cordn_requests_check)) } error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error) } @@ -870,7 +915,7 @@ private fun JoinRequests( if (loaded != null && !busy) { if (loaded.isEmpty()) { Text( - text = stringRes(R.string.cordn_requests_none), + text = stringRes(Res.string.cordn_requests_none), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -899,7 +944,7 @@ private fun JoinRequests( reload() } }) { - Text(stringRes(R.string.cordn_requests_accept)) + Text(stringRes(Res.string.cordn_requests_accept)) } TextButton(onClick = { scope.launch { @@ -911,7 +956,7 @@ private fun JoinRequests( reload() } }) { - Text(stringRes(R.string.cordn_requests_decline)) + Text(stringRes(Res.string.cordn_requests_decline)) } } } @@ -949,9 +994,9 @@ private fun ShareGroup( val clipboard = LocalClipboardManager.current var copied by remember { mutableStateOf(false) } - Text(stringRes(R.string.cordn_share_title), style = MaterialTheme.typography.titleMedium) + Text(stringRes(Res.string.cordn_share_title), style = MaterialTheme.typography.titleMedium) Text( - text = stringRes(R.string.cordn_share_explainer), + text = stringRes(Res.string.cordn_share_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -968,7 +1013,7 @@ private fun ShareGroup( }, modifier = Modifier.padding(top = 8.dp), ) { - Text(stringRes(if (copied) R.string.cordn_share_copied else R.string.cordn_share_copy)) + Text(stringRes(if (copied) Res.string.cordn_share_copied else Res.string.cordn_share_copy)) } } @@ -993,26 +1038,26 @@ private fun EditGroupDetailsDialog( AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringRes(R.string.cordn_info_edit_details)) }, + title = { Text(stringRes(Res.string.cordn_info_edit_details)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { OutlinedTextField( value = draftName, onValueChange = { draftName = it }, - label = { Text(stringRes(R.string.cordn_create_name)) }, + label = { Text(stringRes(Res.string.cordn_create_name)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) OutlinedTextField( value = draftDescription, onValueChange = { draftDescription = it }, - label = { Text(stringRes(R.string.cordn_create_description)) }, + label = { Text(stringRes(Res.string.cordn_create_description)) }, singleLine = false, modifier = Modifier.fillMaxWidth(), ) if (admins.isNotEmpty()) { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_info_admins_kept, admins.size, admins.size), + text = pluralStringRes(Res.plurals.cordn_info_admins_kept, admins.size, admins.size), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -1024,7 +1069,7 @@ private fun EditGroupDetailsDialog( onClick = { onSave(draftName.trim(), draftDescription.trim()) }, enabled = draftName.isNotBlank(), ) { - Text(stringRes(R.string.cordn_info_save)) + Text(stringRes(Res.string.cordn_info_save)) } }, dismissButton = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupListScreen.kt index e7a5defb33..13876ffbe1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupListScreen.kt @@ -41,10 +41,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CoordinatorHealth import com.vitorpamplona.amethyst.commons.cordn.ui.CoordinatorHealthRow import com.vitorpamplona.amethyst.commons.icons.symbols.Icon @@ -52,6 +50,14 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.cordnGroups.CordnGroupChatroom import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_count +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_down_note +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_manage +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_groups_start import com.vitorpamplona.amethyst.commons.resources.cordn_groups_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -106,14 +112,14 @@ fun CordnGroupListScreen( IconButton(onClick = { nav.nav(Route.CordnCoordinators) }) { Icon( symbol = MaterialSymbols.Dns, - contentDescription = stringRes(R.string.cordn_groups_manage), + contentDescription = stringRes(Res.string.cordn_groups_manage), modifier = Modifier.size(22.dp), ) } IconButton(onClick = { nav.nav(Route.CordnCreateGroup) }) { Icon( symbol = MaterialSymbols.Add, - contentDescription = stringRes(R.string.cordn_groups_start), + contentDescription = stringRes(Res.string.cordn_groups_start), modifier = Modifier.size(24.dp), ) } @@ -122,8 +128,8 @@ fun CordnGroupListScreen( ) { padding -> if (runtime == null) { EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -148,8 +154,8 @@ fun CordnGroupListScreen( if (sections.isEmpty()) { EmptyState( - title = stringRes(R.string.cordn_groups_none), - description = stringRes(R.string.cordn_groups_none_detail), + title = stringRes(Res.string.cordn_groups_none), + description = stringRes(Res.string.cordn_groups_none_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -230,7 +236,7 @@ private fun CoordinatorSection( }, ) Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_groups_count, groupCount, groupCount), + text = pluralStringRes(Res.plurals.cordn_groups_count, groupCount, groupCount), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -248,7 +254,7 @@ private fun CoordinatorSection( verticalAlignment = Alignment.Top, ) { Text( - text = stringRes(R.string.cordn_groups_down_note), + text = stringRes(Res.string.cordn_groups_down_note), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnInvitationsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnInvitationsScreen.kt index a1292ad0e6..b145042c85 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnInvitationsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnInvitationsScreen.kt @@ -48,16 +48,30 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.back +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_accept +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_decline +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_decline_warning +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_load_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_members +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_members_more +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_no_coordinators +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_none +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_refresh +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_skipped +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_title +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_unreachable +import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_via import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.model.cordn.CordnInvitation @@ -69,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CoordinatorI import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.coordinatorDisplayName import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.pluralStringResource /** * Invitations to cordn groups, waiting to be answered. @@ -106,7 +121,7 @@ fun CordnInvitationsScreen( var invitations by remember { mutableStateOf(null) } var busy by remember { mutableStateOf(false) } var error by remember { mutableStateOf(null) } - val loadFailed = stringRes(R.string.cordn_invitations_load_failed) + val loadFailed = stringRes(Res.string.cordn_invitations_load_failed) suspend fun reload() { busy = true @@ -130,10 +145,10 @@ fun CordnInvitationsScreen( Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back)) } }, - title = { Text(stringRes(R.string.cordn_invitations_title)) }, + title = { Text(stringRes(Res.string.cordn_invitations_title)) }, actions = { IconButton(onClick = { scope.launch { reload() } }, enabled = !busy) { - Icon(MaterialSymbols.Refresh, contentDescription = stringRes(R.string.cordn_invitations_refresh)) + Icon(MaterialSymbols.Refresh, contentDescription = stringRes(Res.string.cordn_invitations_refresh)) } }, ) @@ -150,8 +165,8 @@ fun CordnInvitationsScreen( ) { if (runtime == null) { EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), ) return@Column } @@ -163,7 +178,7 @@ fun CordnInvitationsScreen( val coordinators by runtime.coordinators.collectAsStateWithLifecycle() Text( - text = stringRes(R.string.cordn_invitations_explainer), + text = stringRes(Res.string.cordn_invitations_explainer), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -212,7 +227,7 @@ fun CordnInvitationsScreen( NoticeCard( title = stringRes( - R.string.cordn_invitations_unreachable, + Res.string.cordn_invitations_unreachable, coordinatorDisplayName(it.coordinator.pubKey, it.coordinator.label, accountViewModel), ), detail = it.reason, @@ -225,7 +240,7 @@ fun CordnInvitationsScreen( // friend swears they sent simply does not exist here. loaded.skipped.forEach { NoticeCard( - title = stringRes(R.string.cordn_invitations_skipped), + title = stringRes(Res.string.cordn_invitations_skipped), detail = it.reason, isError = false, ) @@ -235,9 +250,9 @@ fun CordnInvitationsScreen( Text( text = if (coordinators.isEmpty()) { - stringRes(R.string.cordn_invitations_no_coordinators) + stringRes(Res.string.cordn_invitations_no_coordinators) } else { - stringRes(R.string.cordn_invitations_none) + stringRes(Res.string.cordn_invitations_none) }, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -276,7 +291,7 @@ private fun InvitationCard( // "Already in it", never "invited you" — see the screen KDoc. Text( - text = pluralStringResource(R.plurals.cordn_invitations_members, welcome.members.size, welcome.members.size), + text = pluralStringResource(Res.plurals.cordn_invitations_members, welcome.members.size, welcome.members.size), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -300,7 +315,7 @@ private fun InvitationCard( } if (welcome.members.size > MEMBER_FACES) { Text( - text = stringRes(R.string.cordn_invitations_members_more, welcome.members.size - MEMBER_FACES), + text = stringRes(Res.string.cordn_invitations_members_more, welcome.members.size - MEMBER_FACES), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -329,7 +344,7 @@ private fun InvitationCard( size = 20.dp, ) { name -> Text( - text = stringRes(R.string.cordn_invitations_via, name), + text = stringRes(Res.string.cordn_invitations_via, name), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -340,11 +355,11 @@ private fun InvitationCard( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { - Button(onClick = onAccept) { Text(stringRes(R.string.cordn_invitations_accept)) } - TextButton(onClick = onDecline) { Text(stringRes(R.string.cordn_invitations_decline)) } + Button(onClick = onAccept) { Text(stringRes(Res.string.cordn_invitations_accept)) } + TextButton(onClick = onDecline) { Text(stringRes(Res.string.cordn_invitations_decline)) } } Text( - text = stringRes(R.string.cordn_invitations_decline_warning), + text = stringRes(Res.string.cordn_invitations_decline_warning), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnMessageRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnMessageRow.kt index 189fca42a9..031512faea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnMessageRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnMessageRow.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.chats.ui.ChatDivisor import com.vitorpamplona.amethyst.commons.chats.ui.UserDisplayNameLayout import com.vitorpamplona.amethyst.commons.cordn.CordnMentions @@ -67,6 +66,24 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.copied_to_clipboard import com.vitorpamplona.amethyst.commons.resources.copy_text +import com.vitorpamplona.amethyst.commons.resources.cordn_action_delete +import com.vitorpamplona.amethyst.commons.resources.cordn_action_edit +import com.vitorpamplona.amethyst.commons.resources.cordn_action_go_to_message +import com.vitorpamplona.amethyst.commons.resources.cordn_action_pin +import com.vitorpamplona.amethyst.commons.resources.cordn_action_reply +import com.vitorpamplona.amethyst.commons.resources.cordn_action_unpin +import com.vitorpamplona.amethyst.commons.resources.cordn_chat_unread_divider +import com.vitorpamplona.amethyst.commons.resources.cordn_chat_yesterday +import com.vitorpamplona.amethyst.commons.resources.cordn_delete_confirm_body +import com.vitorpamplona.amethyst.commons.resources.cordn_delete_confirm_title +import com.vitorpamplona.amethyst.commons.resources.cordn_delivery_accepted +import com.vitorpamplona.amethyst.commons.resources.cordn_message_deleted +import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_coordinator +import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_cursor +import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_sent_at +import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_title +import com.vitorpamplona.amethyst.commons.resources.cordn_message_edited +import com.vitorpamplona.amethyst.commons.resources.cordn_reactions_title import com.vitorpamplona.amethyst.commons.resources.quick_action_share import com.vitorpamplona.amethyst.commons.resources.today import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox @@ -292,7 +309,7 @@ private fun CordnBubbleContents( when { text == null -> Text( - text = stringRes(R.string.cordn_message_deleted), + text = stringRes(Res.string.cordn_message_deleted), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -448,7 +465,7 @@ private fun CordnReactionDetailSheet( ) { Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(bottom = 24.dp)) { Text( - text = stringRes(R.string.cordn_reactions_title), + text = stringRes(Res.string.cordn_reactions_title), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp), ) @@ -519,7 +536,7 @@ internal fun CordnQuotedMessage( val body = if (annotations.isDeleted(parent.envelope.id)) { - stringRes(R.string.cordn_message_deleted) + stringRes(Res.string.cordn_message_deleted) } else { annotations.contentOf(parent.envelope.id).orEmpty() } @@ -575,7 +592,7 @@ private fun CordnQuoteActionSheet( Column(Modifier.padding(bottom = 24.dp)) { TileRow { if (onGoToMessage != null) { - ActionTile(MaterialSymbols.ArrowUpward, stringRes(R.string.cordn_action_go_to_message)) { + ActionTile(MaterialSymbols.ArrowUpward, stringRes(Res.string.cordn_action_go_to_message)) { onGoToMessage() onDismiss() } @@ -594,7 +611,7 @@ private fun CordnQuoteActionSheet( */ @Composable internal fun UnreadDivider() { - ChatDivisor(stringRes(R.string.cordn_chat_unread_divider), MaterialTheme.colorScheme.primary) + ChatDivisor(stringRes(Res.string.cordn_chat_unread_divider), MaterialTheme.colorScheme.primary) } /** @@ -618,7 +635,7 @@ private fun CordnMessageFooter( if (isPinned) { Icon( symbol = MaterialSymbols.PushPin, - contentDescription = stringRes(R.string.cordn_action_pin), + contentDescription = stringRes(Res.string.cordn_action_pin), modifier = Modifier.size(12.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -627,7 +644,7 @@ private fun CordnMessageFooter( if (isEdited) { Text( - text = stringRes(R.string.cordn_message_edited), + text = stringRes(Res.string.cordn_message_edited), fontSize = Font12SP, color = MaterialTheme.colorScheme.placeholderText, maxLines = 1, @@ -658,7 +675,7 @@ private fun CordnMessageFooter( Spacer(StdHorzSpacer) Icon( symbol = MaterialSymbols.Done, - contentDescription = stringRes(R.string.cordn_delivery_accepted), + contentDescription = stringRes(Res.string.cordn_delivery_accepted), modifier = Modifier.size(12.dp), tint = MaterialTheme.colorScheme.allGoodColor, ) @@ -711,18 +728,18 @@ private fun CordnMessageDetailsSheet( verticalArrangement = Arrangement.spacedBy(6.dp), ) { Text( - text = stringRes(R.string.cordn_message_details_title), + text = stringRes(Res.string.cordn_message_details_title), style = MaterialTheme.typography.titleSmall, ) - DetailLine(stringRes(R.string.cordn_message_details_sent_at), sentAt) - DetailLine(stringRes(R.string.cordn_message_details_cursor), message.cursor.toString()) + DetailLine(stringRes(Res.string.cordn_message_details_sent_at), sentAt) + DetailLine(stringRes(Res.string.cordn_message_details_cursor), message.cursor.toString()) DetailLine( - stringRes(R.string.cordn_message_details_coordinator), + stringRes(Res.string.cordn_message_details_coordinator), coordinatorDisplayName(coordinatorPubKey, label = null, accountViewModel = accountViewModel), ) if (isMine) { Text( - text = stringRes(R.string.cordn_delivery_accepted), + text = stringRes(Res.string.cordn_delivery_accepted), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.allGoodColor, ) @@ -797,10 +814,10 @@ private fun CordnMessageActionSheet( if (confirmingDelete) { QuickActionAlertDialog( - title = stringRes(R.string.cordn_delete_confirm_title), - textContent = stringRes(R.string.cordn_delete_confirm_body), + title = stringRes(Res.string.cordn_delete_confirm_title), + textContent = stringRes(Res.string.cordn_delete_confirm_body), buttonIcon = MaterialSymbols.Delete, - buttonText = stringRes(R.string.cordn_action_delete), + buttonText = stringRes(Res.string.cordn_action_delete), onClickDoOnce = performDelete, onClickDontShowAgain = { accountViewModel.account.settings.setHideDeleteRequestDialog() @@ -843,7 +860,7 @@ private fun CordnMessageActionSheet( TileRow { if (isLive) { - ActionTile(MaterialSymbols.AutoMirrored.Chat, stringRes(R.string.cordn_action_reply)) { + ActionTile(MaterialSymbols.AutoMirrored.Chat, stringRes(Res.string.cordn_action_reply)) { onReply() onDismiss() } @@ -853,7 +870,7 @@ private fun CordnMessageActionSheet( // message rather than only on your own. ActionTile( MaterialSymbols.PushPin, - stringRes(if (isPinned) R.string.cordn_action_unpin else R.string.cordn_action_pin), + stringRes(if (isPinned) Res.string.cordn_action_unpin else Res.string.cordn_action_pin), ) { onTogglePin() onDismiss() @@ -863,11 +880,11 @@ private fun CordnMessageActionSheet( // anyone else. Hiding them here is the same rule, stated where it stops // being a surprise. if (isMine && isLive) { - ActionTile(MaterialSymbols.Edit, stringRes(R.string.cordn_action_edit)) { + ActionTile(MaterialSymbols.Edit, stringRes(Res.string.cordn_action_edit)) { onEdit() onDismiss() } - ActionTile(MaterialSymbols.Delete, stringRes(R.string.cordn_action_delete), isDestructive = true) { + ActionTile(MaterialSymbols.Delete, stringRes(Res.string.cordn_action_delete), isDestructive = true) { // A withdrawal cannot be taken back and the coordinator keeps // the ciphertext either way, so it is worth one question — // unless the account has already opted out of being asked. @@ -989,7 +1006,7 @@ internal fun DaySeparator( val label = when (day) { today -> stringRes(Res.string.today) - today.minusDays(1) -> stringRes(R.string.cordn_chat_yesterday) + today.minusDays(1) -> stringRes(Res.string.cordn_chat_yesterday) // Year included only when it is not this one: printing 2026 on every // divider all year is noise. else -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt index 3560b60be6..0876d4cf5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt @@ -52,13 +52,26 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_contents_body +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_contents_title +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_export +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_exported +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_passphrase +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_body +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_confirm_body +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_confirm_title +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restored import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_export import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_passphrase import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_restore import com.vitorpamplona.amethyst.commons.resources.cordn_backup_title +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton @@ -113,9 +126,9 @@ fun CordnBackupScreen( var error by remember { mutableStateOf(null) } var pendingRestore by remember { mutableStateOf(null) } - val exported = stringRes(R.string.cordn_backup_exported) - val restored = stringRes(R.string.cordn_backup_restored) - val failed = stringRes(R.string.cordn_backup_failed) + val exported = stringRes(Res.string.cordn_backup_exported) + val restored = stringRes(Res.string.cordn_backup_restored) + val failed = stringRes(Res.string.cordn_backup_failed) val saver = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/octet-stream")) { uri -> @@ -179,8 +192,8 @@ fun CordnBackupScreen( // The app's own empty state, centred and titled, rather than a // sentence stranded in the top-left corner. EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -196,7 +209,7 @@ fun CordnBackupScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { Text( - text = stringRes(R.string.cordn_backup_explainer), + text = stringRes(Res.string.cordn_backup_explainer), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -212,7 +225,7 @@ fun CordnBackupScreen( passphrase = it error = null }, - label = { Text(stringRes(R.string.cordn_backup_passphrase)) }, + label = { Text(stringRes(Res.string.cordn_backup_passphrase)) }, visualTransformation = PasswordVisualTransformation(), // The dots are only half of it. Without the password // keyboard type the IME treats this as ordinary prose: @@ -230,9 +243,9 @@ fun CordnBackupScreen( SettingsSection(Res.string.cordn_backup_section_export) { SettingsFormBlock { - Text(stringRes(R.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall) + Text(stringRes(Res.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall) Text( - text = stringRes(R.string.cordn_backup_contents_body), + text = stringRes(Res.string.cordn_backup_contents_body), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -248,7 +261,7 @@ fun CordnBackupScreen( // Key derivation here is scrypt, which is slow on // purpose, so a greyed-out button was the only sign // anything was happening for several seconds. - BusyLabel(busy, stringRes(R.string.cordn_backup_export)) + BusyLabel(busy, stringRes(Res.string.cordn_backup_export)) } } } @@ -256,7 +269,7 @@ fun CordnBackupScreen( SettingsSection(Res.string.cordn_backup_section_restore) { SettingsFormBlock { Text( - text = stringRes(R.string.cordn_backup_restore_body), + text = stringRes(Res.string.cordn_backup_restore_body), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -266,7 +279,7 @@ fun CordnBackupScreen( enabled = !busy && passphrase.isNotBlank(), modifier = Modifier.fillMaxWidth(), ) { - BusyLabel(busy, stringRes(R.string.cordn_backup_restore)) + BusyLabel(busy, stringRes(Res.string.cordn_backup_restore)) } } } @@ -276,8 +289,8 @@ fun CordnBackupScreen( pendingRestore?.let { uri -> AlertDialog( onDismissRequest = { pendingRestore = null }, - title = { Text(stringRes(R.string.cordn_backup_restore_confirm_title)) }, - text = { Text(stringRes(R.string.cordn_backup_restore_confirm_body)) }, + title = { Text(stringRes(Res.string.cordn_backup_restore_confirm_title)) }, + text = { Text(stringRes(Res.string.cordn_backup_restore_confirm_body)) }, confirmButton = { TextButton(onClick = { pendingRestore = null @@ -302,7 +315,7 @@ fun CordnBackupScreen( } } }) { - Text(stringRes(R.string.cordn_backup_restore), color = MaterialTheme.colorScheme.error) + Text(stringRes(Res.string.cordn_backup_restore), color = MaterialTheme.colorScheme.error) } }, dismissButton = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt index 01b0a3c93f..3459eec409 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt @@ -58,7 +58,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig import com.vitorpamplona.amethyst.commons.cordn.CoordinatorHealth import com.vitorpamplona.amethyst.commons.cordn.CordnCoordinatorDiscovery @@ -68,10 +67,45 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_add_action +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_add_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_copy_key +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_action +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_add +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_none +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_unheard +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_down +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_ok +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_retrying +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_unknown +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_identify +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_key +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_label +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_label_note +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_more +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_none +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge_body +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge_title +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_relays_label +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_remove +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_rename import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_discover import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_manual import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_yours +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server_claim +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server_silent import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_title +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_pubkey +import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_relays +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_info_save import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -127,8 +161,8 @@ fun CordnCoordinatorsScreen( // The app's own empty state, centred and titled, rather than a // sentence stranded in the top-left corner. EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -146,7 +180,7 @@ fun CordnCoordinatorsScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { Text( - text = stringRes(R.string.cordn_coordinators_explainer), + text = stringRes(Res.string.cordn_coordinators_explainer), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -165,7 +199,7 @@ fun CordnCoordinatorsScreen( if (coordinators.isEmpty()) { Text( - text = stringRes(R.string.cordn_coordinators_none), + text = stringRes(Res.string.cordn_coordinators_none), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -225,7 +259,7 @@ private fun CoordinatorCard( IconButton(onClick = { renaming = true }) { Icon( symbol = MaterialSymbols.Edit, - contentDescription = stringRes(R.string.cordn_coordinators_rename), + contentDescription = stringRes(Res.string.cordn_coordinators_rename), modifier = Modifier.size(18.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -264,10 +298,10 @@ private fun CoordinatorCard( // away on the clipboard, which is how a comparison is actually done. val coordinator = remember(config.pubKey) { LocalCache.getOrCreateUser(config.pubKey) } CopyableKeyRow( - label = stringRes(R.string.cordn_coordinators_key), + label = stringRes(Res.string.cordn_coordinators_key), shown = coordinator.pubkeyDisplayHex(), copied = coordinator.pubkeyNpub(), - copyDescription = stringRes(R.string.cordn_coordinators_copy_key), + copyDescription = stringRes(Res.string.cordn_coordinators_copy_key), ) // Every relay, not the first two and a count: this is the management @@ -275,7 +309,7 @@ private fun CoordinatorCard( // came here to check or to rule out. Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( - text = stringRes(R.string.cordn_coordinators_relays_label), + text = stringRes(Res.string.cordn_coordinators_relays_label), style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.SemiBold, ) @@ -304,10 +338,10 @@ private fun CoordinatorCard( text = info?.let { stringRes( - R.string.cordn_coordinators_server, + Res.string.cordn_coordinators_server, listOfNotNull(it.name, it.version, it.protocolVersion).joinToString(" \u00b7 "), ) - } ?: stringRes(R.string.cordn_coordinators_server_silent), + } ?: stringRes(Res.string.cordn_coordinators_server_silent), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -315,7 +349,7 @@ private fun CoordinatorCard( // Said next to it, every time, because a name on a settings // screen reads as verified and this one is not: §8.5 makes // the pubkey the identity and nothing else. - text = stringRes(R.string.cordn_coordinators_server_claim), + text = stringRes(Res.string.cordn_coordinators_server_claim), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -333,7 +367,7 @@ private fun CoordinatorCard( infoChecked = true } }) { - Text(stringRes(R.string.cordn_coordinators_identify)) + Text(stringRes(Res.string.cordn_coordinators_identify)) } Spacer(Modifier.weight(1f)) @@ -345,14 +379,14 @@ private fun CoordinatorCard( IconButton(onClick = { menuOpen = true }) { Icon( symbol = MaterialSymbols.MoreVert, - contentDescription = stringRes(R.string.cordn_coordinators_more), + contentDescription = stringRes(Res.string.cordn_coordinators_more), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) { DropdownMenuItem( - text = { Text(stringRes(R.string.cordn_coordinators_remove)) }, + text = { Text(stringRes(Res.string.cordn_coordinators_remove)) }, onClick = { menuOpen = false scope.launch { runtime.forget(config.pubKey) } @@ -361,7 +395,7 @@ private fun CoordinatorCard( DropdownMenuItem( text = { Text( - text = stringRes(R.string.cordn_coordinators_purge), + text = stringRes(Res.string.cordn_coordinators_purge), color = MaterialTheme.colorScheme.error, ) }, @@ -378,14 +412,14 @@ private fun CoordinatorCard( if (confirmingPurge) { AlertDialog( onDismissRequest = { confirmingPurge = false }, - title = { Text(stringRes(R.string.cordn_coordinators_purge_title)) }, - text = { Text(stringRes(R.string.cordn_coordinators_purge_body)) }, + title = { Text(stringRes(Res.string.cordn_coordinators_purge_title)) }, + text = { Text(stringRes(Res.string.cordn_coordinators_purge_body)) }, confirmButton = { TextButton(onClick = { confirmingPurge = false scope.launch { runtime.purge(config.pubKey) } }) { - Text(stringRes(R.string.cordn_coordinators_purge), color = MaterialTheme.colorScheme.error) + Text(stringRes(Res.string.cordn_coordinators_purge), color = MaterialTheme.colorScheme.error) } }, dismissButton = { @@ -409,10 +443,10 @@ private fun CoordinatorCard( private fun HealthLine(state: CoordinatorHealth.State) { val text = when { - state.isUnknown -> stringRes(R.string.cordn_coordinators_health_unknown) - state.isDown -> stringRes(R.string.cordn_coordinators_health_down, state.consecutiveFailures) - state.consecutiveFailures > 0 -> stringRes(R.string.cordn_coordinators_health_retrying) - else -> stringRes(R.string.cordn_coordinators_health_ok) + state.isUnknown -> stringRes(Res.string.cordn_coordinators_health_unknown) + state.isDown -> stringRes(Res.string.cordn_coordinators_health_down, state.consecutiveFailures) + state.consecutiveFailures > 0 -> stringRes(Res.string.cordn_coordinators_health_retrying) + else -> stringRes(Res.string.cordn_coordinators_health_ok) } Text( @@ -447,10 +481,10 @@ private fun DiscoverCoordinators( var result by remember { mutableStateOf(null) } var busy by remember { mutableStateOf(false) } var error by remember { mutableStateOf(null) } - val failed = stringRes(R.string.cordn_coordinators_discover_failed) + val failed = stringRes(Res.string.cordn_coordinators_discover_failed) Text( - text = stringRes(R.string.cordn_coordinators_discover_explainer), + text = stringRes(Res.string.cordn_coordinators_discover_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -475,7 +509,7 @@ private fun DiscoverCoordinators( CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp) Spacer(Modifier.width(8.dp)) } - Text(stringRes(R.string.cordn_coordinators_discover_action)) + Text(stringRes(Res.string.cordn_coordinators_discover_action)) } error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error) } @@ -497,11 +531,11 @@ private fun DiscoverCoordinators( Text( text = if (found.unreachable.isEmpty()) { - stringRes(R.string.cordn_coordinators_discover_none) + stringRes(Res.string.cordn_coordinators_discover_none) } else { // "Nobody is announcing" and "we were not told" are // different answers and only one of them is final. - stringRes(R.string.cordn_coordinators_discover_unheard, found.unreachable.size) + stringRes(Res.string.cordn_coordinators_discover_unheard, found.unreachable.size) }, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -529,19 +563,19 @@ private fun RenameCoordinatorDialog( AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringRes(R.string.cordn_coordinators_rename)) }, + title = { Text(stringRes(Res.string.cordn_coordinators_rename)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { OutlinedTextField( value = input, onValueChange = { input = it }, - label = { Text(stringRes(R.string.cordn_coordinators_label)) }, + label = { Text(stringRes(Res.string.cordn_coordinators_label)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) Text( // The same caution the add form gives, for the same reason. - text = stringRes(R.string.cordn_coordinators_label_note), + text = stringRes(Res.string.cordn_coordinators_label_note), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -549,7 +583,7 @@ private fun RenameCoordinatorDialog( }, confirmButton = { TextButton(onClick = { onSave(input.trim().ifEmpty { null }) }) { - Text(stringRes(R.string.cordn_info_save)) + Text(stringRes(Res.string.cordn_info_save)) } }, dismissButton = { @@ -601,7 +635,7 @@ private fun DiscoveredCard( Text( // Staleness matters more here than anywhere: the last live // survey found most announcements were months-dead demos. - text = stringRes(R.string.cordn_coordinators_discover_seen, timeAgoNoDot(offer.announcedAt).trim()), + text = stringRes(Res.string.cordn_coordinators_discover_seen, timeAgoNoDot(offer.announcedAt).trim()), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -619,7 +653,7 @@ private fun DiscoveredCard( enabled = !busy, modifier = Modifier.padding(top = 4.dp), ) { - Text(stringRes(R.string.cordn_coordinators_discover_add)) + Text(stringRes(Res.string.cordn_coordinators_discover_add)) } } } @@ -633,7 +667,7 @@ private fun AddCoordinator(runtime: CordnRuntime) { var labelInput by remember { mutableStateOf("") } var error by remember { mutableStateOf(null) } var busy by remember { mutableStateOf(false) } - val failed = stringRes(R.string.cordn_coordinators_add_failed) + val failed = stringRes(Res.string.cordn_coordinators_add_failed) val config = remember(pubKeyInput, relaysInput, labelInput) { @@ -652,7 +686,7 @@ private fun AddCoordinator(runtime: CordnRuntime) { pubKeyInput = it error = null }, - label = { Text(stringRes(R.string.cordn_create_coordinator_pubkey)) }, + label = { Text(stringRes(Res.string.cordn_create_coordinator_pubkey)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) @@ -662,14 +696,14 @@ private fun AddCoordinator(runtime: CordnRuntime) { relaysInput = it error = null }, - label = { Text(stringRes(R.string.cordn_create_coordinator_relays)) }, + label = { Text(stringRes(Res.string.cordn_create_coordinator_relays)) }, singleLine = false, modifier = Modifier.fillMaxWidth(), ) OutlinedTextField( value = labelInput, onValueChange = { labelInput = it }, - label = { Text(stringRes(R.string.cordn_coordinators_label)) }, + label = { Text(stringRes(Res.string.cordn_coordinators_label)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) @@ -677,7 +711,7 @@ private fun AddCoordinator(runtime: CordnRuntime) { Text( // A label is ours, never theirs. A coordinator cannot prove a name and // this one is not asked for one. - text = stringRes(R.string.cordn_coordinators_label_note), + text = stringRes(Res.string.cordn_coordinators_label_note), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -705,6 +739,6 @@ private fun AddCoordinator(runtime: CordnRuntime) { enabled = !busy && config != null, modifier = Modifier.fillMaxWidth(), ) { - Text(stringRes(R.string.cordn_coordinators_add_action)) + Text(stringRes(Res.string.cordn_coordinators_add_action)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt index 5f33e13b24..b3ebf82863 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt @@ -47,14 +47,28 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_none +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_disclosure +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_explainer +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_last_resort_no +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_last_resort_yes +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_orphans +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_orphans_body +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_publish +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_publish_last_resort import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_section +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_summary import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_title +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_topup_note +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_withdraw_all +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_withdraw_orphans import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton @@ -106,8 +120,8 @@ fun CordnKeyPackagesScreen( ) { padding -> if (runtime == null) { EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold @@ -125,7 +139,7 @@ fun CordnKeyPackagesScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { Text( - text = stringRes(R.string.cordn_keypackages_explainer), + text = stringRes(Res.string.cordn_keypackages_explainer), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -140,7 +154,7 @@ fun CordnKeyPackagesScreen( SettingsFormBlock { if (coordinators.isEmpty()) { Text( - text = stringRes(R.string.cordn_coordinators_none), + text = stringRes(Res.string.cordn_coordinators_none), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -169,7 +183,7 @@ private fun CoordinatorKeyPackages( var rows by remember(config.pubKey) { mutableStateOf?>(null) } var busy by remember(config.pubKey) { mutableStateOf(false) } var error by remember(config.pubKey) { mutableStateOf(null) } - val failed = stringRes(R.string.cordn_keypackages_failed) + val failed = stringRes(Res.string.cordn_keypackages_failed) suspend fun reload() { busy = true @@ -204,15 +218,15 @@ private fun CoordinatorKeyPackages( val hasLastResort = loaded.any { it.lastResort } Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_keypackages_summary, single, single), + text = pluralStringRes(Res.plurals.cordn_keypackages_summary, single, single), style = MaterialTheme.typography.bodyMedium, ) Text( text = if (hasLastResort) { - stringRes(R.string.cordn_keypackages_last_resort_yes) + stringRes(Res.string.cordn_keypackages_last_resort_yes) } else { - stringRes(R.string.cordn_keypackages_last_resort_no) + stringRes(Res.string.cordn_keypackages_last_resort_no) }, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -225,11 +239,11 @@ private fun CoordinatorKeyPackages( ) { Column(Modifier.padding(12.dp)) { Text( - text = pluralStringRes(LocalContext.current, R.plurals.cordn_keypackages_orphans, orphans.size, orphans.size), + text = pluralStringRes(Res.plurals.cordn_keypackages_orphans, orphans.size, orphans.size), style = MaterialTheme.typography.titleSmall, ) Text( - text = stringRes(R.string.cordn_keypackages_orphans_body), + text = stringRes(Res.string.cordn_keypackages_orphans_body), style = MaterialTheme.typography.bodySmall, ) TextButton(onClick = { @@ -242,7 +256,7 @@ private fun CoordinatorKeyPackages( reload() } }) { - Text(stringRes(R.string.cordn_keypackages_withdraw_orphans)) + Text(stringRes(Res.string.cordn_keypackages_withdraw_orphans)) } } } @@ -251,7 +265,7 @@ private fun CoordinatorKeyPackages( Text( // Above the buttons, because it is the part that cannot be taken // back: §8.4 makes publishing an attributable, permanent record. - text = stringRes(R.string.cordn_keypackages_disclosure), + text = stringRes(Res.string.cordn_keypackages_disclosure), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -274,7 +288,7 @@ private fun CoordinatorKeyPackages( }, enabled = !busy, ) { - Text(stringRes(R.string.cordn_keypackages_publish)) + Text(stringRes(Res.string.cordn_keypackages_publish)) } OutlinedButton( onClick = { @@ -289,7 +303,7 @@ private fun CoordinatorKeyPackages( }, enabled = !busy && !hasLastResort, ) { - Text(stringRes(R.string.cordn_keypackages_publish_last_resort)) + Text(stringRes(Res.string.cordn_keypackages_publish_last_resort)) } if (loaded.isNotEmpty()) { TextButton( @@ -305,7 +319,7 @@ private fun CoordinatorKeyPackages( }, enabled = !busy, ) { - Text(stringRes(R.string.cordn_keypackages_withdraw_all), color = MaterialTheme.colorScheme.error) + Text(stringRes(Res.string.cordn_keypackages_withdraw_all), color = MaterialTheme.colorScheme.error) } } } @@ -313,7 +327,7 @@ private fun CoordinatorKeyPackages( Text( // The rule CordnRuntime.maintainKeyPackages implements, said where // someone can see that it applies to them. - text = stringRes(R.string.cordn_keypackages_topup_note), + text = stringRes(Res.string.cordn_keypackages_topup_note), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt index 86aab6c17c..f4ac911967 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CordnLinkInspection import com.vitorpamplona.amethyst.commons.cordn.ui.CordnExposureCard import com.vitorpamplona.amethyst.commons.resources.Res @@ -60,6 +59,11 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_link_no_coordinator import com.vitorpamplona.amethyst.commons.resources.cordn_link_not_joinable import com.vitorpamplona.amethyst.commons.resources.cordn_link_paste import com.vitorpamplona.amethyst.commons.resources.cordn_link_relays +import com.vitorpamplona.amethyst.commons.resources.cordn_link_request +import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_disclosure +import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_failed +import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_sent +import com.vitorpamplona.amethyst.commons.resources.cordn_link_scan import com.vitorpamplona.amethyst.commons.resources.cordn_link_section_input import com.vitorpamplona.amethyst.commons.resources.cordn_link_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -102,8 +106,8 @@ fun CordnLinkScreen( var requesting by remember { mutableStateOf(false) } val runtime = accountViewModel.account.cordnRuntime val scope = rememberCoroutineScope() - val requestFailed = stringRes(R.string.cordn_link_request_failed) - val asked = stringRes(R.string.cordn_link_request_sent) + val requestFailed = stringRes(Res.string.cordn_link_request_failed) + val asked = stringRes(Res.string.cordn_link_request_sent) var scanning by remember { mutableStateOf(false) } val clipboard = LocalClipboardManager.current @@ -163,7 +167,7 @@ fun CordnLinkScreen( Text(stringResource(Res.string.cordn_link_paste)) } OutlinedButton(onClick = { scanning = true }) { - Text(stringRes(R.string.cordn_link_scan)) + Text(stringRes(Res.string.cordn_link_scan)) } if (input.isNotEmpty()) { OutlinedButton( @@ -265,7 +269,7 @@ fun CordnLinkScreen( enabled = !requesting, modifier = Modifier.fillMaxWidth(), ) { - Text(stringRes(R.string.cordn_link_request)) + Text(stringRes(Res.string.cordn_link_request)) } Text( @@ -274,7 +278,7 @@ fun CordnLinkScreen( // KeyPackage under this account's own key and tells // the coordinator this account wants into this // group, whether or not anyone ever answers (§8.4). - text = stringRes(R.string.cordn_link_request_disclosure), + text = stringRes(Res.string.cordn_link_request_disclosure), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt index db9b3d4d4e..6c55c2bbb0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt @@ -47,9 +47,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CordnMigration import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable +import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_cancel import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_cancel_desc import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_code_note @@ -119,8 +120,8 @@ fun CordnMigrateScreen( ) { padding -> if (runtime == null) { EmptyState( - title = stringRes(R.string.cordn_group_unavailable), - description = stringRes(R.string.cordn_group_unavailable_detail), + title = stringRes(Res.string.cordn_group_unavailable), + description = stringRes(Res.string.cordn_group_unavailable_detail), modifier = Modifier.padding(padding), ) return@Scaffold diff --git a/amethyst/src/main/res/values-hi-rIN/strings.xml b/amethyst/src/main/res/values-hi-rIN/strings.xml index 60879f7b96..8343082ebf 100644 --- a/amethyst/src/main/res/values-hi-rIN/strings.xml +++ b/amethyst/src/main/res/values-hi-rIN/strings.xml @@ -353,322 +353,4 @@ ध्वनि सक्रिय। मौन करने के लिए टाँकें चलाएँ विराम - आपने अपना %1$s अन्य क्रमक में परिवर्तित किया - हटाए गए (%1$s) - जोडे गए (%1$s) - परिवर्तित (%1$s) - आपने सभी निजी (रहस्यीकृत) वस्तुओं को हटा दिया। - आपने निजी (रहस्यीकृत) वस्तुओं का परिवर्तन भी किया। उनकी तुलना यहाँ नहीं की जा सकती। - आपने निजी (रहस्यीकृत) वस्तुओं को जोडा। - सुरक्षित संस्करण पुनःस्थापन - इस यन्त्र पर - नया संस्करण - स्थापित नहीं - %1$s हटाए गए - %1$s जोडे गए - %1$s परिवर्तित - - आपने %1$d व्यक्ति का अनुगमन हटाया - आपने %1$d व्यक्तियों का अनुगमन हटाए - - स्वीकार - %1$s अद्यतनकृत - %1$s पारित - अन्य क्रमक में परिवर्तित। %1$s - रखें %1$s - पुनःस्थापन %1$s - पुनःमौन %1$s - पुनःजुडें %1$s - परिचय पुनःस्थापध - पुनःप्रसारक पुनःस्थापन - %1$s रखे गए - %1$s परित्यक्त - %1$s नए - परित्यक्त। %1$s - नए। %1$s - सम्पादित। %1$s - %1$s लोग खोजें - यहाँ कुछ नहीं - %1$s मौनावस्था हटाए गए - ये पुनः आपके सूचनावलियों तक अभिगम्य - %1$s नए मौनकृत - शब्द तथा विषयसूचक - %1$s तथा %2$s अधिक - आपने सभी निजी वस्तुओं को हटा दिया - वे रहस्यीकृत हैं। इसलिए केवल आप देख सकते हैं कि वे क्या थे। पुनःस्थापन से सब लौटके आ जाएँगे। - आपने वस्तुओं को निजी किया - वे अब रहस्यीकृत हैं। जो कि सम्भाव्यतः ऊपर के सार्वजनिक वस्तुओं का गन्तव्य था। - आपने निजी वस्तुओं का परिवर्तन किया - वे रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। - चला गया - नव्य - परित्यक्त - स्थानान्तरित - इकाई - शेष - पुनःनामकृत - निर्गतपेटिका - आप जहाँ प्रकाशित करते हैं - आगतपेटिका - जहाँ अन्य आप तक अभिगमन करते हैं - ग्राहक कुंचिका प्रतिस्थापित - ग्राहक कुंचिका हटाया गया - नटज्साप आगे से इस कुंचिका से बद्ध होंगे। यदि इस यन्त्र में वह नहीं है तो आप सम्भाव्यतः उनको यहाँ नहीं चुका सकते। - शेष - जुड गए - पुनःनामकृत - पूर्वतः %1$s था - पुनःबाधित करें %1$s को - सूचनावलियाँ पुनःस्थापन - विषय पुनःस्थापन - स्थल पुनःस्थापन - प्रदाता पुनःस्थापन - धनकोष पुनःस्थापन - रखे गए - ग्रहण मध्य - सूचीबद्ध - खोज मध्य - अभिलेखन मध्य - विश्वसनीय - अविश्वसनीय - %1$s अबाधित - अमेथिस्ट पुनः उनसे जुडेगा - अब से बाधित नहीं - नव्यतः बाधित - अभी भी बाधित - अनुमत - स्थानान्तरित - सूचनावली - विषय हटाए गए - स्थल हटाए गए - नव्य - विषयसूचक जिनका आप अनुगमन करते हैं - स्थल जिनका आप अनुगमन करते हैं - %2$s में से %1$s सेवाएँ परिवर्तित - वे अब अन्य व्यक्ति से आते है। अथवा किसी से नहीं - सेवा - श्रेणी - अनुचर - विषयसूची - श्रेणीयाँ तथा अनुचर गिनती तथा कचरालेख छालन जो आप देखते हैं इन प्रदाताओं से आते हैं। - - आपको भुगतान करने की %1$d विधि हटाया गया - आपको भुगतान करने की %1$d विधियाँ हटाए गए - - - %1$d प्रस्ताव हटाया गया - %1$d प्रस्ताव हटाए गए - - लोग जो आपको भुगतान करते हैं उसी को देखते हैं जो इस सूची में है। - बोल्ट॰बारह प्रस्ताव - आपने उसे रिक्त किया - आपने उसे बनाया - आपने उसे परिवर्तित किया - आपके धनकोष के टकसाल तथा कुंचिकाएँ आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना वस्तु प्रति वस्तु यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया। - आपके कोंकोर्ड समुदाय आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया। - - आपने %1$d की मौनावस्था हटाया - आपने %1$d की मौनावस्था हटाए - - - आपने %1$d परिचय प्रपत्रस्थान हटाया - आपने %1$d परिचय प्रपत्रस्थान हटाए - - - आपने %1$d सार्वजनिक पुनःप्रसारक हटाया - आपने %1$d सार्वजनिक पुनःप्रसारकों को हटाए - - - आपने %1$d सीधासन्देश पुनःप्रसारक हटाया - आपने %1$d सीधासन्देश पुनःप्रसारकों को हटाए - - - आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारक हटाया - आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारकों को हटाए - - - आपने %1$d खोज पुनःप्रसारक हटाया - आपने %1$d खोज पुनःप्रसारकों को हटाए - - - आपने %1$d अनुक्रमणीकार पुनःप्रसारक हटाया - आपने %1$d अनुक्रमणीकार पुनःप्रसारकों को हटाए - - - आपने %1$d पुनःप्रसारक सूचनावली हटाया - आपने %1$d पुनःप्रसारक सूचनावलियाँ हटाए - - - आपने %1$d निजी पुनःप्रसारक हटाया - आपने %1$d निजी पुनःप्रसारकों को हटाए - - - आपने %1$d पुनःप्रसारक पर विश्वास करना रोक दिया - आपने %1$d पुनःप्रसारकों पर विश्वास करना रोक दिए - - - आपने %1$d पुनःप्रसारक अबाधित किया - आपने %1$d पुनःप्रसारकों को अबाधित किए - - - आपने %1$d सार्वजनिक चर्चा छोड दिया - आपने %1$d सार्वजनिक चर्चाओं को छोड दिए - - - आपने %1$d समुदाय छोड दिया - आपने %1$d समुदायों को छोड दिए - - - आपने %1$d चर्चाशाला छोड दिया - आपने %1$d चर्चाशालाओं को छोड दिए - - - आपने %1$d समूह छोड दिया - आपने %1$d समूहों को छोड दिए - - - आपने %1$d इष्ट सूचनावली हटाया - आपने %1$d इष्ट सूचनावलियों को हटाए - - - आपने %1$d विषयसूचक का अनुगमन हटाया - आपने %1$d विषयसूचकों का अनुगमन हटाए - - - आपने %1$d स्थल का अनुगमन हटाया - आपने %1$d स्थलों का अनुगमन हटाए - - - आपने %1$d विश्वाससेवा प्रदाता परिवर्तित किया - आपने %1$d विश्वाससेवाओं के प्रदाता को परिवर्तित किया - - - आपने %1$d नटज्साप टकसाल हटाया - आपने %1$d नटज्साप टकसालों को हटाए - - - आपने %1$d भुगतान प्राप्त करने की विधि हटाया - आपने %1$d भुगतान प्राप्त करने के विधियों को हटाए - - - आपने %1$d बोल्ट॰बारह प्रस्ताव हटाया - आपने %1$d बोल्ट॰बारह प्रस्तावों को हटाए - - - %1$s तथा %2$d जोडा - %1$s तथा %2$d जोडे - - - %1$s तथा %2$d का अनुगमन किया - %1$s तथा %2$d का अनुगमन किए - - - %1$s तथा %2$d को मौनकृत किया - %1$s तथा %2$d को मौनकृत किए - - - %1$s तथा %2$d से जुडे - %1$s तथा %2$d से जुडे - - - %1$s तथा %2$d बाधित किया - %1$s तथा %2$d को बाधित किए - - आपके अमेथिस्ट समचरणीकृत क्रमक स्थापना विकल्पों को परिवर्तित किया गया एक अन्य क्रमक में और वह अमेथिस्ट नहीं था। इससे समस्याएँ उत्पन्न हो सकते हैं। - आपने अपनी नटज्साप कुंचिका का प्रतिस्थापन किया - आपने अपनी नटज्साप कुंचिका को हटाया - आपने अपना काशयू धनकोष रिक्त किया - आपने अपने काशयू धनकोष को परिवर्तित किया - आपने अपनी कोंकोर्ड सूची रिक्त की - आपने अपनी कोंकोर्ड सूची का परिवर्तन किया - नया संस्करण का उपयोग करें - पठनेवशक्य - लेखनेवशक्य - पठन तथा लेखन - परिचय - आपका नाम चित्र चरित तथा भुगतान पते। जैसे सब को दिखते हैं वैसे। - अनुचरण सूची - निर्णय करता है किसके पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। - मौन सूची - छिपाता है लोग शब्द विषयसूचक तथा सूत्र उन सब में से जो आप देखते हैं। - सार्वजनिक पुनःप्रसारक सूची - सबको बताता है आपके पत्र कहाँ प्राप्त करें तथा आपको उत्तर तथा उल्लेखों को कहाँ भेजें। - सीधासन्देश पुनःप्रसारक सूची - जहाँ अन्य आपके निजी सन्देश भेजते हैं। - समूहचर्चा कुंचिका पुनःप्रसारक - जहाँ अन्य उन कुंचिकाओं को प्राप्त करते हैं आपको रहस्यीकृत समूहचर्चाओं में जोडने के लिए। - खोज पुनःप्रसारक सूची - उपयुक्त पुनःप्रसारक जब आप खोजते हैं। - अनुक्रमणीकार पुनःप्रसारक सूची - पुनःप्रसारक उपयुक्त अन्य लोगों के परिचय तथा पुनःप्रसारक सूचियों को प्राप्त करने के लिए। - इष्ट पुनःप्रसारक सूचनावलियाँ - पुनःप्रसारक जिनका आप वीक्षण करते हैं सूचनावलियों के रूप में। - बाधित पुनःप्रसारक सूची - पुनःप्रसारक जिनसे यह क्रमक कभी भी संयोजन नहीं करता। - विश्वसनीय पुनःप्रसारक सूची - पुनःप्रसारक जिनपर आप पर्याप्त विश्वास करते हैं बिना पूछे उनसे संयोजन करने के लिए। - निजी पुनःप्रसारक सूची - जहाँ आपके पाण्डुलिपियाँ तथा निजी जानकारी रखे जाते हैं। - समचरणीकृत क्रमक स्थापना विकल्प - अमेथिस्ट स्थापना विकल्प समचरणीकृत आपके यन्त्रों में। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। - सार्वजनिक चर्चा सूची - सार्वजनिक चर्चाएँ जिनसे आप जुडे। - समुदाय सूची - समुदाय जिनसे आप जुडे। - अनुचरित विषयसूचक - विषयसूचक जिससे युक्त पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। - अनुचरित स्थान - स्थान जहाँ से पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। - इष्ट सूचनावली सूची - कलनविध्यात्मक सूचनावलियाँ जिनको आपने अभिलेखित किया। - अस्थायी चर्चा सूची - अस्थायी चर्चा शालाएँ जिनसे आप जुडे। - समूह सूची - पुनःप्रसारक आधारित समूह जिनसे आप जुडे। - कोंकोर्ड समुदाय सूची - कोंकोर्ड समुदाय जिनसे आप जुडे। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। - विश्वाससेवा प्रदाता सूची - सेवाएँ जिनपर आप विश्वास करते है आपके लिए लोगों का सत्यापन तथा श्रेणीकरण करने के लिए। - काशयू धनकोष - आपके ईकाश धनकोष स्थापना विकल्प तथा टकसाल। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। - नटज्साप प्राप्ती स्थापना विकल्प - टकसाल तथा कुंचिका जिनका उपयोग अन्य करते हैं आपको नटज्साप भेजने के लिए। - भुगतान पते - विधियाँ जिनसे अन्य लोग आपको भुगतान कर सके। - बोल्ट॰बारह प्रस्ताव - लैटनिंग प्रस्ताव जिनका उपयोग करके अन्य लोग आपको भुगतान करते हैं। - सूची - इस यन्त्र पर अभिलेखित सूची। - परिचय प्रपत्रस्थान - लोग - विषयसूचक - शब्द - सूत्र - सार्वजनिक चर्चाएँ - समुदाय - सूचनावलियाँ - स्थान - पुनःप्रसारक - समूह - चर्चा शालाएँ - विश्वाससेवा प्रदाता - टकसाल - नटज्साप कुंचिकाएँ - भुगतान पते - प्रस्ताव - सम्बन्धित व्यक्तिगण - अन्य प्रविष्टियाँ - प्रयोक्तानाम - प्रदर्शन नाम - जानकारी - चित्र - ध्वज - जालस्थान - नोस्टर पता - लैटनिंग पता - लैटनिंग जालपता - सर्वनाम - जन्मदिन - क्लिंक॰ प्रस्ताव - क्रमचर चिह्न diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml index e7a9da119d..37bd743202 100644 --- a/amethyst/src/main/res/values-hu-rHU/strings.xml +++ b/amethyst/src/main/res/values-hu-rHU/strings.xml @@ -353,322 +353,4 @@ Hang bekapcsolva. Koppintson a némításhoz Lejátszás Szüneteltetés - Ön módosította a következőt: %1$s, egy másik alkalmazásban - Eltávolítva (%1$s) - Hozzáadva (%1$s) - Módosítva (%1$s) - Ön eltávolította az összes privát (titkosított) elemet. - Ön privát (titkosított) elemeket is módosított. Ezek itt nem hasonlíthatók össze. - Ön privát (titkosított) elemeket adott hozzá. - Mentett verzió visszaállítása - Ezen az eszközön - Új verzió - Nincs beállítva - %1$s eltávolítva - %1$s hozzáadva - %1$s módosítva - - %1$d személyt nem követ többé - %1$d személyt nem követ többé - - Elfogadás - %1$s frissítve - %1$s törölve - Módosítva egy másik alkalmazásban · %1$s - %1$s megtartása - %1$s visszaállítása - %1$s újbóli némítása - Újbóli csatlakozás ehhez: %1$s - Profil visszaállítása - Átjátszók visszaállítása - %1$s megtartva - %1$s elvetve - %1$s új - Elvetve · %1$s - Új · %1$s - Szerkesztve · %1$s - %1$s személy keresése - Itt nincs semmi - %1$s némítása feloldva - Ezek újra megjelenhetnek a hírfolyamaiban - %1$s újonnan némítva - Szavak és kulcsszavak - %1$s és %2$s további - Ön eltávolította az összes privát elemet - Titkosítva vannak, így csak Ön láthatja, mik voltak. A visszaállítás mindet visszahozza. - Ön priváttá tette az elemeket - Most már titkosítva vannak, valószínűleg ide kerültek a fenti nyilvános elemek. - Ön módosította a privát elemeket - Titkosítva vannak, így itt nem hasonlíthatók össze. - Eltűnt - Új - Elvetve - Áthelyezve - Egységek - Elhagyva - Átnevezve - Kimenő - ahol közzétesz - Bejövő - ahol mások elérik Önt - Fogadási kulcs lecserélve - Fogadási kulcs eltávolítva - A nutzapok ehhez az új kulcshoz lesznek kötve. Ha ez az eszköz nem rendelkezik vele, lehet, hogy itt nem tudja beváltani őket. - elhagyta a csoportot - csatlakozott - átnevezte - „%1$s” volt - %1$s újbóli letiltása - Hírfolyamok visszaállítása - Témák visszaállítása - Helyek visszaállítása - Szolgáltatók visszaállítása - Pénztárca visszaállítása - megtartva - Fogadás - Listázva - Keresés - Tárolás - Megbízható - Nem megbízható - %1$s letiltása feloldva - Az Amethyst újra kapcsolódni fog hozzájuk - Már nincs letiltva - Újonnan letiltva - Továbbra is le van tiltva - Engedélyezve - Áthelyezve - Hírfolyam - eltűnt témák - eltűnt helyek - új - Ön által követett kulcsszavak - Ön által követett helyek - %2$s szolgáltatásból %1$s módosult - Most már valaki mástól vagy senkitől sem származnak - Szolgáltatás - Rang - Követők - Témák - A látott rangok, követőszámok és a kéretlen tartalomszűrés ezektől a szolgáltatóktól származnak. - - %1$d fizetési mód eltávolítva - %1$d fizetési mód eltávolítva - - - %1$d ajánlat eltávolítva - %1$d ajánlat eltávolítva - - A fizető emberek csak azt látják, ami ebben a listában van. - BOLT12 ajánlat - Ön kiürítette - Ön hozta létre - Ön módosította - A saját pénztárca pénzverdéi és kulcsai Önhöz vannak titkosítva, így itt nem hasonlíthatók össze tételesen. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett. - A Concord közösségei Önhöz vannak titkosítva, így itt nem hasonlíthatók össze. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett. - - Ön %1$d dolog némítását oldotta fel - %1$d dolog némítását oldotta fel - - - Ön %1$d profilmezőt távolított el - %1$d profilmezőt távolított el - - - Ön %1$d nyilvános átjátszót távolított el - %1$d nyilvános átjátszót távolított el - - - Ön %1$d privát üzenetátjátszót távolított el - %1$d privát üzenet átjátszót távolított el - - - Ön %1$d csoportos csevegési kulcsátjátszót távolított el - %1$d csoportcsevegés kulcs átjátszót távolított el - - - Ön %1$d kereső átjátszót távolított el - %1$d kereső átjátszót távolított el - - - Ön %1$d indexelő átjátszót távolított el - %1$d indexelő átjátszót távolított el - - - Ön %1$d átjátszó-hírfolyamot távolított el - %1$d átjátszó hírfolyamot távolított el - - - Ön %1$d privát átjátszót távolított el - %1$d privát átjátszót távolított el - - - Ön %1$d átjátszóban nem bízik többé - %1$d átjátszóban nem bízik többé - - - Ön %1$d átjátszó letiltását oldotta fel - %1$d átjátszó blokkolását oldotta fel - - - Ön %1$d nyilvános csevegést hagyott el - %1$d nyilvános csevegést hagyott el - - - Ön %1$d közösséget hagyott el - %1$d közösséget hagyott el - - - Ön %1$d csevegőszobát hagyott el - %1$d csevegőszobát hagyott el - - - Ön %1$d csoportot hagyott el - %1$d csoportot hagyott el - - - Ön %1$d kedvenc hírfolyamot távolított el - %1$d kedvenc hírfolyamot távolított el - - - Ön %1$d kulcsszót nem követ többé - %1$d hashtaget nem követ többé - - - Ön %1$d helyet nem követ többé - %1$d helyet nem követ többé - - - Ön %1$d bizalmi szolgáltatás szolgáltatóját módosította - %1$d bizalmi szolgáltatás szolgáltatóját változtatta meg - - - Ön %1$d nutzap pénzverdét távolított el - %1$d nutzap pénzverdét távolított el - - - Ön %1$d fizetési módot távolított el - %1$d fizetési módot távolított el - - - Ön %1$d BOLT12 ajánlatot távolított el - %1$d BOLT12 ajánlatot távolított el - - - %1$s és %2$d hozzáadva - %1$s és %2$d hozzáadva - - - %1$s és %2$d követve - %1$s és %2$d követve - - - %1$s és %2$d némítva - %1$s és %2$d némítva - - - %1$s és %2$d csatlakozva - %1$s és %2$d csatlakozva - - - %1$s és %2$d letiltva - %1$s és %2$d letiltva - - A szinkronizált Amethyst alkalmazásbeállításai egy másik, nem Amethyst alkalmazásban módosultak. Ez problémákat okozhat. - Ön lecserélte a nutzap kulcsát - Ön eltávolította a nutzap kulcsát - Ön kiürítette a Cashu pénztárcáját - Ön módosította a Cashu pénztárcáját - Ön kiürítette a Concord listáját - Ön módosította a Concord listáját - Új verzió használata - csak olvasható - csak írható - olvasás és írás - profil - Az Ön neve, képe, bemutatkozása és fizetési címei, ahogy mindenki látja őket. - követési lista - Meghatározza, kinek a bejegyzései jelenjenek meg a kezdőlapon. - némítási lista - Elrejti a személyeket, szavakat, kulcsszavakat és beszélgetéseket mindenből, amit lát. - nyilvános átjátszólista - Megmondja mindenkinek, hol találhatók a bejegyzései, és hová küldjék a válaszokat és említéseket. - privát üzenet átjátszólista - Ahová mások kézbesítik a privát üzeneteit. - csoportos csevegési kulcsátjátszók - Ahol mások megtalálják a kulcsokat, hogy titkosított csoportos csevegésekhez adhassák Önt. - kereső átjátszólista - Kereséskor használt átjátszók. - indexelő átjátszólista - Azok az átjátszók, amelyeket mások profiljainak és átjátszólistáinak megtalálásához használnak. - kedvenc átjátszóhírfolyamok - Olyan átjátszók, amelyeket hírfolyamként böngész. - letiltott átjátszólista - Átjátszók, amelyekhez ez az alkalmazás soha nem kapcsolódik. - megbízható átjátszólista - Átjátszók, amelyekhez kérdezés nélkül kapcsolódhat. - privát átjátszólista - Ahol a piszkozatai és privát adatai tárolódnak. - szinkronizált alkalmazásbeállítások - Az eszközök között szinkronizált Amethyst-beállítások. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. - nyilvános csevegési lista - Nyilvános csevegések, amelyekhez csatlakozott. - közösséglista - Közösségek, amelyekhez csatlakozott. - követett kulcsszavak - Kulcsszavak, amelyek bejegyzései megjelennek a hírfolyamban. - követett helyek - Helyek, amelyek bejegyzései megjelennek a hírfolyamaiban. - kedvenc hírfolyamlista - Mentett algoritmus-hírfolyamok. - rövid életű csevegési lista - Rövid életű csevegőszobák, amelyekhez csatlakozott. - csoportlista - Átjátszó-alapú csoportok, amelyekhez csatlakozott. - Concord közösséglista - A Concord közösségek, amelyekhez csatlakozott. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. - bizalmi szolgáltatólista - A szolgáltatások, amelyekben megbízik, hogy rangsorolják és ellenőrizzék Önnek az embereket. - Cashu pénztárca - Az ecash pénztárca beállításai és pénzverdéi. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. - nutzap fogadási beállítások - A pénzverdék és a kulcs, amelyeket mások a nutzapok küldéséhez használnak az Ön számára. - fizetési címek - A módok, ahogy mások fizethetnek Önnek. - BOLT12 ajánlatok - A lightning ajánlatok, amelyeket mások a fizetéshez használnak az Ön számára. - lista - Egy listaelem ezen az eszközön mentve. - Profilmezők - Személyek - Kulcsszavak - Szavak - Beszélgetések - Nyilvános csevegések - Közösségek - Hírfolyamok - Helyek - Átjátszók - Csoportok - Csevegőszobák - Bizalmi szolgáltatók - Pénzverdék - Nutzap kulcsok - Fizetési címek - Ajánlatok - Kapcsolt identitások - Egyéb bejegyzések - Felhasználónév - Megjelenített név - Névjegy - Kép - Borító - Weboldal - Nostr-cím - Lightning-cím - LNURL - Névmások - Születésnap - CLINK-ajánlat - Bot jelölő diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index 601165a320..eda8c8fe1a 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -381,382 +381,4 @@ Dźwięk włączony. Kliknij, aby wyciszyć Odtwórz Pauza - Zmieniłeś %1$s w innej aplikacji - Usunięto (%1$s) - Dodano (%1$s) - Zmieniono (%1$s) - Usunąłeś wszystkie prywatne (zaszyfrowane) elementy. - Zmieniłeś również elementy prywatne (zaszyfrowane). Nie można ich tutaj porównać. - Dodano prywatne (zaszyfrowane) pozycje. - Przywróć zapisaną wersję - Na tym urządzeniu - Nowa wersja - Nie ustawiono - Usunięto %1$s - Dodano %1$s - %1$s zmieniono - - Przestałeś obserwować %1$d osobę - Przestałeś obserwować %1$d osób - Przestałeś obserwować %1$d osób - Przestałeś obserwować %1$d osoby - - Zaakceptuj - %1$s zaktualizowano - %1$s wyczyszczono - Zmieniono w innej aplikacji · %1$s - Zachowaj %1$s - Przywróć %1$s - Ponownie wycisz %1$s - Ponów %1$s - Przywróć profil - Przywróć transmitery - Zachowano %1$s - %1$s odrzuconych - %1$s nowych - Usunięte · %1$s - Nowe · %1$s - Edytowane · %1$s - Szukaj %1$s osób - Nic tu nie ma! - %1$s odblokowanych - Mogą one ponownie pojawić się w Twoich informacjach - %1$s nowo wyciszonych - Wyrazy & hasztagi - %1$s i %2$s więcej - Usunąłeś wszystkie prywatne elementy - Są zaszyfrowane, więc tylko Ty możesz zobaczyć, co to było. Odtworzenie przywróci je wszystkie. - Ustawiłeś te pozycje jako prywatne - Są teraz zaszyfrowane, co prawdopodobnie wyjaśnia, gdzie podziały się powyższe elementy publiczne. - Zmieniłeś prywatne elementy - Są zaszyfrowane, więc nie można ich tu porównać. - Zniknęła - Nowa - Usunięta - Przeniesiona - Jednostek - Zostało - Zmieniono nazwę - Skrzynka nadawcza - gdzie publikujesz - Skrzynka odbiorcza - gdzie inni docierają do Ciebie - Wymieniono klucz odbierający - Usunięto klucz odbierający - Nutzapy zostaną przypisane do tego nowego klucza. Jeśli urządzenie nie posiada tego klucza, może się okazać, że nie będzie można ich tutaj zrealizować. - zostało - dołączył(a) - zmieniono nazwę - było „%1$s” - Zablokuj %1$s ponownie - Przywróć kanały - Przywróć tematy - Przywróć miejsca - Przywróć dostawców - Przywróć portfel - zachowane - Pobieranie - Na Liście - Szukam - Przechowywanie - Zaufane - Niezaufane - %1$s odblokowane - Ametyst ponownie połączy się z nimi - Nie jest już zablokowana - Nowo zablokowana - Nadal zablokowana - Dozwolona - Przeniesiona - Kanał - tematy zniknęły - miejsca zniknęły - nowe - Hashtagi, które obserwujesz - Miejsca które obserwujesz - Zmieniono %1$s z %2$s serwisów - Teraz pochodzą od kogoś innego albo od nikogo - Serwis - Ranking - Obserwujący - Tematy - Rankingi, liczba obserwujących i filtrowanie spamu, które widzisz, pochodzą od tych dostawców. - - %1$d sposób płatności został usunięty - %1$d sposobów płatności zostało usuniętych - %1$d sposobów płatności zostało usuniętych - %1$d sposoby płatności zostały usunięte - - - %1$d oferta usunięta - %1$d ofert usunięto - %1$d ofert usunięto - %1$d oferty usunięte - - Osoby, które Ci płacą, widzą tylko to, co znajduje się na tej liście. - Oferta BOLT12 - Opróżniłeś to - Utworzyłeś to - Zmieniłeś to - Minty i klucze w Twoim portfelu są zaszyfrowane dla Ciebie, więc nie można ich tutaj porównać pozycja po pozycji. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu. - Twoje społeczności w Concord są dla Ciebie zaszyfrowane, więc nie można ich tutaj porównać. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu. - - Włączyłeś dźwięk dla %1$d elementu - Włączyłeś dźwięk dla %1$d elementów - Włączyłeś dźwięk dla %1$d elementów - %1$d elementy mają włączony dźwięk - - - Usunąłeś %1$d pole profilu - Usunąłeś %1$d pól profilowych - Usunąłeś %1$d pól profilowych - Usunąłeś %1$d pola profilowe - - - Usunąłeś %1$d publiczny transmiter - Usunąłeś %1$d publicznych transmiterów - Usunąłeś %1$d publicznych transmiterów - Usunąłeś %1$d publiczne transmitery - - - Usunąłeś %1$d transmiter DM - Usunąłeś %1$d transmiterów DM - Usunąłeś %1$d transmiterów DM - Usunąłeś %1$d transmitery DM - - - Usunięto %1$d transmiter klucza grupowego czatu - Usunięto %1$d transmiterów klucza grupowego czatu - Usunięto %1$d transmiterów klucza grupowego czatu - Usunięto %1$d transmitery klucza grupowego czatu - - - Usunięto %1$d transmiter wyszukiwania - Usunięto %1$d transmiterów wyszukiwania - Usunięto %1$d transmiterów wyszukiwania - Usunięto %1$d transmitery wyszukiwania - - - Usunięto %1$d transmiter indeksujący - Usunięto %1$d transmiterów indeksujących - Usunięto %1$d transmiterów indeksujących - Usunięto %1$d transmitery indeksujące - - - Usunięto %1$d kanał transmitera - Usunięto %1$d kanałów transmitera - Usunięto %1$d kanałów transmitera - Usunięto %1$d kanały transmitera - - - Usunięto %1$d prywatny transmiter - Usunięto %1$d prywatnych transmiterów - Usunięto %1$d prywatnych transmiterów - Usunięto %1$d prywatne transmitery - - - Przestałeś ufać %1$d transmiterowi - Przestałeś ufać %1$d transmiterom - Przestałeś ufać %1$d transmiterom - Przestałeś ufać %1$d transmiterom - - - Odblokowałeś %1$d transmiter - Odblokowałeś %1$d transmiterów - Odblokowałeś %1$d transmiterów - Odblokowałeś %1$d transmitery - - - Opuściłeś %1$d czat publiczny - Opuściłeś %1$d czatów publicznych - Opuściłeś %1$d czatów publicznych - Opuściłeś %1$d czaty publiczne - - - Opuściłeś %1$d społeczność - Opuściłeś %1$d społeczności - Opuściłeś %1$d społeczności - Opuściłeś %1$d społeczności - - - Opuściłeś %1$d pokój czatu - Opuściłeś %1$d pokoi czatu - Opuściłeś %1$d pokoi czatu - Opuściłeś %1$d pokoje czatu - - - Opuściłeś %1$d grupę - Opuściłeś %1$d grup - Opuściłeś %1$d grup - Opuszczono %1$d grupy - - - Usunąłeś %1$d ulubiony kanał - Usunąłeś %1$d ulubionych kanałów - Usunąłeś %1$d ulubionych kanałów - Usunięto %1$d ulubione kanały - - - Przestałeś obserwować %1$d hashtag - Przestałeś obserwować %1$d hashtagów - Przestałeś obserwować %1$d hashtagów - Przestałeś obserwować %1$d hashtagi - - - Przestałeś obserwować %1$d miejsce - Przestałeś obserwować %1$d miejsc - Przestałeś obserwować %1$d miejsc - Przestałeś obserwować %1$d miejsca - - - Zmieniłeś dostawcę %1$d zaufanego serwisu - Zmieniłeś dostawcę %1$d zaufanych serwisów - Zmieniłeś dostawcę %1$d zaufanych serwisów - Zmieniłeś dostawcę %1$d zaufanych serwisów - - - Usunięto %1$d nutzap mint - Usunięto %1$d nutzap mintów - Usunięto %1$d nutzap mintów - Usunięto %1$d nutzap minty - - - Usunięto %1$d sposób na otrzymanie zapłaty - Usunięto %1$d sposobów otrzymywania zapłaty - Usunięto %1$d sposobów otrzymywania zapłaty - Usunięto %1$d sposoby otrzymywania zapłaty - - - Usunięto %1$d ofertę BOLT12 - Usunięto %1$d ofert BOLT12 - Usunięto %1$d ofert BOLT12 - Usunięto %1$d oferty BOLT12 - - - %1$s i dodano %2$d - %1$s i dodano %2$d - %1$s i dodano %2$d - %1$s i dodano %2$d - - - %1$s i obserwowany %2$d - %1$s i obserwowanych %2$d - %1$s i obserwowanych %2$d - %1$s i obserwowane %2$d - - - %1$s i wyciszony %2$d - %1$s i wyciszono %2$d - %1$s i wyciszono %2$d - %1$s i wyciszone %2$d - - - %1$s i dołączono do %2$d - %1$s i dołączono do %2$d - %1$s i dołączono do %2$d - %1$s i dołączono do %2$d - - - %1$s i zablokowano %2$d - %1$s i zablokowano %2$d - %1$s i zablokowano %2$d - %1$s i zablokowano %2$d - - Twoje zsynchronizowane ustawienia aplikacji Amethyst zostały zmienione w innej aplikacji, która nie jest Amethystem. To może powodować problemy. - Wymieniłeś swój klucz Nutzap - Usunąłeś swój klucz nutzap - Opróżniłeś swój portfel Cashu - Zmieniłeś swój portfel Cashu - Opróżniłeś swoją listę Concorda - Zmieniłeś swoją listę Concorda - Użyj nowej wersji - tylko do odczytu - tylko do zapisu - odczyt i zapis - profil - Twoje imię i nazwisko, zdjęcie, biografia oraz adresy do płatności – tak, jak widzą je wszyscy. - lista obserwowanych - Określa, czyje posty pojawiają się w Twoim kanale głównym. - lista zablokowanych - Ukrywa osoby, słowa, hashtagi i wątki we wszystkich wyświetlanych treściach. - lista publicznych transmiterów - Informuje wszystkich, gdzie mogą znaleźć Twoje posty oraz gdzie mają wysyłać Ci odpowiedzi i wzmianki. - Lista transmiterów DM - Gdzie inni dostarczają Twoje prywatne wiadomości. - transmitery kluczy czatu grupowego - Gdzie inni znajdą klucze do dodawania cię do zaszyfrowanych czatów grupowych. - lista transmiterów wyszukiwania - Transmitery używane podczas wyszukiwania. - lista transmiterów indeksujących - Transmitery służące do wyszukiwania profili innych osób oraz list transmiterów. - ulubione kanały transmitera - Transmitery, które przeglądasz jako kanały informacyjne. - lista zablokowanych przekaźników - Transmitery, z którymi ta aplikacja nigdy się nie łączy. - lista zaufanych transmiterów - Zaufane transmitery, z którymi bez wahania nawiązujesz połączenie. - lista prywatnych transmiterów - Gdzie przechowywane są Twoje projekty i prywatne dane. - zsynchronizowane ustawienia aplikacji - Ustawienia Amethyst są synchronizowane na wszystkich Twoich urządzeniach. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich obecności. - lista czatów publicznych - Publiczne czaty, do których dołączyłeś. - lista społeczności - Społeczności, do których dołączyłeś. - obserwowane hashtagi - Hashtagi, z których pochodzą posty wyświetlane w Twoich kanałach. - obserwowane lokalizacje - Miejsca, z których posty pojawiają się na Twoich kanałach. - lista ulubionych kanałów - Zapisane przez Ciebie kanały algorytmiczne. - lista czatów efemerycznych - Tymczasowe czaty, do których dołączyłeś. - lista grup - Grupy oparte na transmiterach, do których dołączyłeś. - Lista społeczności Concord - Społeczności Concord, do których dołączyłeś. Są one zaszyfrowane, więc można porównać jedynie fakt ich istnienia. - lista zaufanych dostawców - Serwisy, którym ufasz, a które zajmują się klasyfikowaniem i weryfikacją osób w Twoim imieniu. - Portfel Cashu - Ustawienia portfela ecash oraz minty. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich istnienia. - ustawienia odbioru nutzap - Minty i klucz, które inni używają do wysyłania ci „nutzapów”. - adresy płatności - Sposoby, w jakie inni mogą Ci zapłacić. - Oferta BOLT12 - Ta możliwość pozwala innym płacić Ci za pomocą Lightning. - lista - Lista zapisana na tym urządzeniu. - Pola profilu - Osoby - Hashtagi - Słowa - Wątki - Czaty publiczne - Społeczności - Kanały - Lokalizacje - Transmitery - Grupy - Czat pokoje - Zaufani dostawcy - Minty - Klucze Nutzap - Adresy płatności - Oferty - Powiązane tożsamości - Inne wpisy - Nazwa użytkownika - Nazwa wyświetlana - O profilu - Zdjęcie - Baner - Strona www - Adres Nostr - Lightning Adres - LNURL - Zaimki - Data urodzin - Oferta CLINK - Flaga bota diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 2da4657d8d..3803e00f8a 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -305,250 +305,6 @@ - Coordinators - Nothing is arriving from these groups. Amethyst keeps retrying. - No cordn groups yet - A cordn group lives on one coordinator that holds its membership and passes messages along, without ever seeing what anyone writes. - Start one - This group is not available. Its coordinator may have been removed. - Group info - Message deleted - edited - Go to message - Withdraw message - Everyone in the group will see this message as withdrawn, and Amethyst will stop showing its text. The coordinator still holds the encrypted copy it was sent as, and anyone who already read it has already read it. - Accepted by the coordinator - Message details - Sent - Cursor - Coordinator - Message - Reply - Edit - Delete - Pin - Previous pinned message - Next pinned message - Show all pinned messages - Pinned messages - Pinned by %1$s - - %1$d pinned message in this group - %1$d pinned messages in this group - - Unpin - Editing your message - Could not send that file. - That did not send. - This group\'s coordinator is not open, so nothing can be sent yet. - Could not open that file. - No media server is set for this account, so there is nowhere to put the file. Pick one in Settings. - That file could not be read. - Record a voice note - Stop and send - Play voice note - Your nsec cannot bring cordn groups back. A cordn group lives as encryption state on this device and an ordered stream on a coordinator that cannot read it — lose the device without a backup and the group is gone, including everything already said in it. - Passphrase - What is in the file - Your group encryption keys, your key packages, and every message in those groups. Anyone who opens it can read the conversations. The passphrase is the only thing protecting it once it leaves Amethyst. - Export - Backup saved. - Restore - Restoring replaces the cordn groups on this device with the ones in the file. It is for a phone that has taken over from another — not a way to use the same groups on two devices at once. - Restore - Replace this device\'s cordn groups? - Every cordn group now on this device will be removed and replaced by the ones in the file.\n\nDo not restore onto a second phone while the first is still in use. Both would hold the same group keys, and once both have sent a message the group breaks for everyone — silently, and with no way back. - Restored. - That did not work. Check the passphrase and the file. - Send - Coordinator - Coordinator key - Coordinator link - Copy the coordinator\'s nprofile - Group id - Epoch - Members - This group has no admins: everyone can add, remove and rename, permanently. - Technical details - New Cordn group - A cordn group is ordered by one coordinator you choose. Creating the group happens on this device — the coordinator only learns the group exists once you invite someone or send a message. - Nobody yet — tap to add people - Nobody added yet. A group can be created empty and invitations sent later, but naming people now is what shows which coordinator can reach them. - Done - You, always an admin - Name it - Who is in it - Where it lives - Who can add and remove - Add someone - Naming people now is what tells us which coordinator can reach them. Each one gets a Welcome left on that coordinator, which they pick up next time they open their app. - Not checked yet - No key on any coordinator you use - Everyone can add and remove - Admin - No coordinator chosen - Change - Done - Not checked — picking it will ask - Reaches all %1$d of your people - Reaches %1$d of %2$d — the rest would be left out - The group exists - Each invitation is a Welcome left on the coordinator, so they can fail one at a time. Nobody has been notified: they see it next time they open their app. - Added to the group, but the invitation did not reach them — try again from the group screen - Waiting for them - The coordinator refused the invitation - No key here, so no Welcome could be left - Send a link - Open the chat - Coordinator - Find coordinators - Another coordinator - Coordinator public key (hex or npub) - Relays it answers on, one per line - Group name - Description (optional) - Everyone in the group can invite, remove members and change its details. Any member can name admins later, which ends that. - Only I manage this group - That change could not be made. - Remove - Remove from group? - %1$s stops receiving messages from here. They keep what they have already read, and this cannot be undone — they would have to be invited again. - Edit details - Save - Only you can invite, remove members or change the group\'s details. You can hand that to someone else later. - Create group - The group could not be created. - Cordn invitations - Checked when you open this screen, and at no other time — asking a coordinator whether anyone invited you tells it you are here, so Amethyst does not do it in the background. - Check again - Join - Decline - Declining is permanent. Getting back in means being invited again. - Yesterday - New messages - No messages yet - Anything sent here is encrypted for this group. The coordinator relays it without being able to read it. - Reactions - Add a coordinator in Settings to start using cordn groups. - Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked. - Look for coordinators - Add - Could not read announcements - Nobody is announcing on your relays. - Nothing found, and %1$d of your relays did not answer. - Announced %1$s ago - Last announced %1$s - %1$s - %1$s +%2$d more - Show all %1$d - Show fewer - Show %1$d that stopped announcing - Hide the ones that stopped announcing - These have not announced in over a month. Creating a group on one that has gone away will fail. - Add someone - Name, npub or name@domain - Can be added on this coordinator - They can only be added if they published a key package to this coordinator. - What this coordinator can see - Got it - That person - %1$s has not published a key package to this coordinator, so they cannot be added yet. Ask them to open cordn on this coordinator first. - This coordinator served a key package that belongs to someone else, so %1$s was not added. Nothing was changed. - %1$s could not be given a way into the group, so the add was abandoned. - %1$s is not in this group. - You cannot remove yourself from a cordn group. - The coordinator did not answer, so nothing was changed. Try again in a moment. - Nobody found by that name. - Admin - +%1$d more - Through %1$s - No invitations waiting. - You have no coordinator open. cordn invitations arrive through a coordinator, so there is nowhere to look yet. - %1$s did not answer - An invitation was left for another device - Could not read invitations. - Ask to join this group - This publishes a key package under your own account key and tells the coordinator you want into this group — whether or not anyone answers. - Asked. A member of the group has to add you; the invitation will show up under cordn invitations. - Could not ask to join. - Requests to join - Check for requests - Nobody is waiting to join. - Add - Dismiss - Only an admin can accept. In a group with no admins, that is everyone. - Could not read the requests. - Scan - Share this group - Anyone with this link can ask to join. An admin still has to let them in. - Copy link - Copied - A coordinator orders one group\'s messages and is the only thing that can. Losing it loses the ordering, and a second one does not mirror the first — so these are named things you choose, not interchangeable relays. - No coordinators yet. - Add - Could not open that coordinator. - A name for it (yours, optional) - Answers on - Coordinator key - Copy the coordinator\'s npub - More actions - Rename - This name is yours and stays on this device. A coordinator cannot prove a name, so it is never asked for one. - Ask who it is - Says it is: %1$s - Answered, but named nothing. - A coordinator\'s name and version are strings it chose. Its public key is the only thing that identifies it. - Answering. - A call failed; retrying. - Not answering — %1$d calls in a row have failed. Groups on it are not syncing. - Nothing asked of it yet. - Remove - Purge - Purge this coordinator? - Removing keeps the groups on this device, so adding the coordinator back brings them with it. Purging deletes them: the group keys, the read positions and your key packages all go, and those groups can only be re-entered by a fresh invitation.\n\nThe coordinator is not told and keeps whatever it already had. - Others can only add you to a cordn group by taking a key package you published to that coordinator. cordn has no other place to keep one, so with none published nobody can invite you — and nothing tells them why. - - %1$d person cannot be reached by any coordinator you use - %1$d people cannot be reached by any coordinator you use - - - %1$d group - %1$d groups - - - Reachable on %1$d coordinator - Reachable on %1$d coordinators - - - %1$d person can add and remove - %1$d people can add and remove - - - Create and invite %1$d person - Create and invite %1$d people - - - %1$d member - %1$d members - - - %1$d single-use package available. - %1$d single-use packages available. - - A last-resort package is published, so an invitation can still be made once the single-use ones run out. - No last-resort package. Once the single-use ones run out, invitations fail. - - %1$d package this device cannot open - %1$d packages this device cannot open - - The coordinator will hand these to anyone inviting you, but the private half is not on this device — it belongs to another install. If that install is gone, an invitation using one of these produces a welcome nobody can ever open. - Withdraw those - Publishing signs a record under your own account key. That coordinator then knows this account exists and is invitable — permanently, and whether or not anyone invites you. - Publish one - Publish last-resort - Withdraw all - Once you have published here, Amethyst quietly replaces packages as they are used up. It never publishes the first one for you. - Could not read the key packages. Forum channel Threaded posts instead of a chat timeline. This cannot be changed later. @@ -899,10 +655,6 @@ - - %1$d person is already in this group - %1$d people are already in this group - Private Message Chat message Nest — Live @@ -931,326 +683,4 @@ Sound on. Click to mute Play Pause - You changed your %1$s in another app - Removed (%1$s) - Added (%1$s) - Changed (%1$s) - You removed all private (encrypted) items. - You also changed private (encrypted) items. They can\'t be compared here. - You added private (encrypted) items. - Restore saved version - On this device - New version - Not set - %1$s removed - %1$s added - %1$s changed - - You unfollowed %1$d person - You unfollowed %1$d people - - Accept - %1$s updated - %1$s cleared - Changed in another app · %1$s - Keep %1$s - Restore %1$s - Re-mute %1$s - Rejoin %1$s - Restore profile - Restore relays - %1$s kept - %1$s dropped - %1$s new - Dropped · %1$s - New · %1$s - Edited · %1$s - Search %1$s people - Nothing here - %1$s unmuted - These can reach your feeds again - %1$s newly muted - Words & hashtags - %1$s and %2$s more - You removed all private items - They\'re encrypted, so only you can see what they were. Restoring brings them all back. - You made items private - They\'re encrypted now, which is likely where the public items above went. - You changed private items - They\'re encrypted, so they can\'t be compared here. - Gone - New - Dropped - Moved - Units - Left - Renamed - Outbox - where you publish - Inbox - where others reach you - Receiving key replaced - Receiving key removed - Nutzaps will be locked to this new key. If this device doesn\'t have it, you may not be able to redeem them here. - left - joined - renamed - was “%1$s” - Block %1$s again - Restore feeds - Restore topics - Restore places - Restore providers - Restore wallet - kept - Receiving - Listed - Searching - Storing - Trusted - Untrusted - %1$s unblocked - Amethyst will connect to them again - No longer blocked - Newly blocked - Still blocked - Allowed - Moved - Feed - topics gone - places gone - new - Hashtags you follow - Places you follow - %1$s of %2$s services changed - They now come from someone else, or no one - Service - Rank - Followers - Topics - Ranks, follower counts and spam filtering you see come from these providers. - - %1$d way to pay you removed - %1$d ways to pay you removed - - - %1$d offer removed - %1$d offers removed - - People paying you only see what\'s in this list. - BOLT12 offer - You emptied it - You created it - You changed it - Your wallet\'s mints and keys are encrypted to you, so they can\'t be compared item by item here. Restoring puts back exactly what this device saved. - Your Concord communities are encrypted to you, so they can\'t be compared here. Restoring puts back exactly what this device saved. - - You unmuted %1$d thing - You unmuted %1$d things - - - You removed %1$d profile field - You removed %1$d profile fields - - - You removed %1$d public relay - You removed %1$d public relays - - - You removed %1$d DM relay - You removed %1$d DM relays - - - You removed %1$d group-chat key relay - You removed %1$d group-chat key relays - - - You removed %1$d search relay - You removed %1$d search relays - - - You removed %1$d indexer relay - You removed %1$d indexer relays - - - You removed %1$d relay feed - You removed %1$d relay feeds - - - You removed %1$d private relay - You removed %1$d private relays - - - You stopped trusting %1$d relay - You stopped trusting %1$d relays - - - You unblocked %1$d relay - You unblocked %1$d relays - - - You left %1$d public chat - You left %1$d public chats - - - You left %1$d community - You left %1$d communities - - - You left %1$d chat room - You left %1$d chat rooms - - - You left %1$d group - You left %1$d groups - - - You removed %1$d favorite feed - You removed %1$d favorite feeds - - - You unfollowed %1$d hashtag - You unfollowed %1$d hashtags - - - You unfollowed %1$d place - You unfollowed %1$d places - - - You changed the provider of %1$d trust service - You changed the provider of %1$d trust services - - - You removed %1$d nutzap mint - You removed %1$d nutzap mints - - - You removed %1$d way to get paid - You removed %1$d ways to get paid - - - You removed %1$d BOLT12 offer - You removed %1$d BOLT12 offers - - - %1$s and added %2$d - %1$s and added %2$d - - - %1$s and followed %2$d - %1$s and followed %2$d - - - %1$s and muted %2$d - %1$s and muted %2$d - - - %1$s and joined %2$d - %1$s and joined %2$d - - - %1$s and blocked %2$d - %1$s and blocked %2$d - - Your Amethyst synced app settings changed in another app that is not Amethyst. This can create problems. - You replaced your nutzap key - You removed your nutzap key - You emptied your Cashu wallet - You changed your Cashu wallet - You emptied your Concord list - You changed your Concord list - Use new version - read only - write only - read and write - profile - Your name, picture, bio and payment addresses, as everyone sees them. - follow list - Decides whose posts appear in your home feed. - mute list - Hides people, words, hashtags and threads from everything you see. - public relay list - Tells everyone where to find your posts and where to send you replies and mentions. - DM relay list - Where others deliver your private messages. - group-chat key relays - Where others find the keys to add you to encrypted group chats. - search relay list - The relays used when you search. - indexer relay list - The relays used to find other people\'s profiles and relay lists. - favorite relay feeds - The relays you browse as feeds. - blocked relay list - Relays this app never connects to. - trusted relay list - Relays you trust to connect to without asking. - private relay list - Where your drafts and private data are stored. - synced app settings - Amethyst settings synced across your devices. They are encrypted, so only their presence can be compared. - public chat list - The public chats you joined. - community list - The communities you joined. - followed hashtags - Hashtags whose posts appear in your feeds. - followed locations - Locations whose posts appear in your feeds. - favorite feeds list - The algorithmic feeds you saved. - ephemeral chat list - The ephemeral chat rooms you joined. - group list - The relay-based groups you joined. - Concord community list - The Concord communities you joined. They are encrypted, so only their presence can be compared. - trust provider list - The services you trust to rank and verify people for you. - Cashu wallet - Your ecash wallet settings and mints. They are encrypted, so only their presence can be compared. - nutzap receiving settings - The mints and key others use to send you nutzaps. - payment addresses - The ways other people can pay you. - BOLT12 offers - The lightning offers others use to pay you. - list - A list saved on this device. - Profile fields - People - Hashtags - Words - Threads - Public chats - Communities - Feeds - Locations - Relays - Groups - Chat rooms - Trust providers - Mints - Nutzap keys - Payment addresses - Offers - Linked identities - Other entries - Username - Display name - About - Picture - Banner - Website - Nostr address - Lightning address - LNURL - Pronouns - Birthday - CLINK offer - Bot flag - - The group\'s %1$d admin stays as it is. - The group\'s %1$d admins stay as they are. - diff --git a/commonsUI/ARCHITECTURE.md b/commonsUI/ARCHITECTURE.md index b693e06a2b..8a091c05f7 100644 --- a/commonsUI/ARCHITECTURE.md +++ b/commonsUI/ARCHITECTURE.md @@ -58,7 +58,10 @@ is specific to the UI module. `tools/icon-font/build_icon_font.py` read `icons/` and write `composeResources/font/` in this module (see root `.claude/CLAUDE.md`, "Icons"). - Translations: `crowdin.yml` and `tools/strings-migrate/` target - `commonsUI/src/commonMain/composeResources/`. + `commonsUI/src/commonMain/composeResources/`. This is where **every new + user-visible string** goes, Android-only screens included; the app's own + `res/values/strings.xml` holds only the synchronous-platform tier (see root + `.claude/CLAUDE.md`, "Strings"). - CI: `.github/workflows/build.yml` runs `:commonsUI:jvmTest`, `:commonsUI:verifyKmpPurity` and the iOS compile/test tasks next to the `:commons` ones. diff --git a/commonsUI/src/commonMain/composeResources/values-hi-rIN/strings.xml b/commonsUI/src/commonMain/composeResources/values-hi-rIN/strings.xml index 3a1f286d3d..2d16c9c513 100644 --- a/commonsUI/src/commonMain/composeResources/values-hi-rIN/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-hi-rIN/strings.xml @@ -5286,4 +5286,322 @@ हस्ताक्षररहित। इसलिए यह कर्ता केवल एक कथन है प्रकार %1$d का वस्तु। जिसे यह ग्राहक चित्रित नहीं करता %1$d अष्टकों का अमुक जिसे यह ग्राहक पढता नहीं + पुनःबाधित करें %1$s को + रखें %1$s + पुनःजुडें %1$s + पुनःमौन %1$s + पुनःस्थापन %1$s + सूचनावलियाँ पुनःस्थापन + स्थल पुनःस्थापन + परिचय पुनःस्थापध + प्रदाता पुनःस्थापन + पुनःप्रसारक पुनःस्थापन + विषय पुनःस्थापन + धनकोष पुनःस्थापन + स्वीकार + + %1$s तथा %2$d जोडा + %1$s तथा %2$d जोडे + + + %1$s तथा %2$d बाधित किया + %1$s तथा %2$d को बाधित किए + + + %1$s तथा %2$d का अनुगमन किया + %1$s तथा %2$d का अनुगमन किए + + + %1$s तथा %2$d से जुडे + %1$s तथा %2$d से जुडे + + + %1$s तथा %2$d को मौनकृत किया + %1$s तथा %2$d को मौनकृत किए + + आपके अमेथिस्ट समचरणीकृत क्रमक स्थापना विकल्पों को परिवर्तित किया गया एक अन्य क्रमक में और वह अमेथिस्ट नहीं था। इससे समस्याएँ उत्पन्न हो सकते हैं। + आपने अपनी कोंकोर्ड सूची रिक्त की + आपने अपनी कोंकोर्ड सूची का परिवर्तन किया + + आपने %1$d सीधासन्देश पुनःप्रसारक हटाया + आपने %1$d सीधासन्देश पुनःप्रसारकों को हटाए + + + आपने %1$d इष्ट सूचनावली हटाया + आपने %1$d इष्ट सूचनावलियों को हटाए + + + आपने %1$d व्यक्ति का अनुगमन हटाया + आपने %1$d व्यक्तियों का अनुगमन हटाए + + + आपने %1$d विषयसूचक का अनुगमन हटाया + आपने %1$d विषयसूचकों का अनुगमन हटाए + + + आपने %1$d अनुक्रमणीकार पुनःप्रसारक हटाया + आपने %1$d अनुक्रमणीकार पुनःप्रसारकों को हटाए + + + आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारक हटाया + आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारकों को हटाए + + + आपने %1$d सार्वजनिक चर्चा छोड दिया + आपने %1$d सार्वजनिक चर्चाओं को छोड दिए + + + आपने %1$d समुदाय छोड दिया + आपने %1$d समुदायों को छोड दिए + + + आपने %1$d समूह छोड दिया + आपने %1$d समूहों को छोड दिए + + + आपने %1$d चर्चाशाला छोड दिया + आपने %1$d चर्चाशालाओं को छोड दिए + + + आपने %1$d की मौनावस्था हटाया + आपने %1$d की मौनावस्था हटाए + + + आपने %1$d सार्वजनिक पुनःप्रसारक हटाया + आपने %1$d सार्वजनिक पुनःप्रसारकों को हटाए + + आपने अपनी नटज्साप कुंचिका को हटाया + आपने अपनी नटज्साप कुंचिका का प्रतिस्थापन किया + + आपने %1$d नटज्साप टकसाल हटाया + आपने %1$d नटज्साप टकसालों को हटाए + + + आपने %1$d बोल्ट॰बारह प्रस्ताव हटाया + आपने %1$d बोल्ट॰बारह प्रस्तावों को हटाए + + + आपने %1$d भुगतान प्राप्त करने की विधि हटाया + आपने %1$d भुगतान प्राप्त करने के विधियों को हटाए + + + आपने %1$d स्थल का अनुगमन हटाया + आपने %1$d स्थलों का अनुगमन हटाए + + + आपने %1$d निजी पुनःप्रसारक हटाया + आपने %1$d निजी पुनःप्रसारकों को हटाए + + + आपने %1$d परिचय प्रपत्रस्थान हटाया + आपने %1$d परिचय प्रपत्रस्थान हटाए + + + आपने %1$d पुनःप्रसारक सूचनावली हटाया + आपने %1$d पुनःप्रसारक सूचनावलियाँ हटाए + + + आपने %1$d खोज पुनःप्रसारक हटाया + आपने %1$d खोज पुनःप्रसारकों को हटाए + + + आपने %1$d विश्वाससेवा प्रदाता परिवर्तित किया + आपने %1$d विश्वाससेवाओं के प्रदाता को परिवर्तित किया + + + आपने %1$d पुनःप्रसारक पर विश्वास करना रोक दिया + आपने %1$d पुनःप्रसारकों पर विश्वास करना रोक दिए + + + आपने %1$d पुनःप्रसारक अबाधित किया + आपने %1$d पुनःप्रसारकों को अबाधित किए + + आपने अपना काशयू धनकोष रिक्त किया + आपने अपने काशयू धनकोष को परिवर्तित किया + जोडे गए (%1$s) + परिवर्तित (%1$s) + %1$s जोडे गए + %1$s परिवर्तित + %1$s हटाए गए + नया संस्करण का उपयोग करें + आपने निजी (रहस्यीकृत) वस्तुओं को जोडा। + आपने निजी (रहस्यीकृत) वस्तुओं का परिवर्तन भी किया। उनकी तुलना यहाँ नहीं की जा सकती। + आपने सभी निजी (रहस्यीकृत) वस्तुओं को हटा दिया। + पठनेवशक्य + पठन तथा लेखन + लेखनेवशक्य + हटाए गए (%1$s) + सुरक्षित संस्करण पुनःस्थापन + आपने अपना %1$s अन्य क्रमक में परिवर्तित किया + सूचनावलियाँ + चर्चा शालाएँ + समुदाय + विषयसूचक + सम्बन्धित व्यक्तिगण + स्थान + टकसाल + नटज्साप कुंचिकाएँ + प्रस्ताव + अन्य प्रविष्टियाँ + भुगतान पते + लोग + परिचय प्रपत्रस्थान + सार्वजनिक चर्चाएँ + पुनःप्रसारक + समूह + सूत्र + विश्वाससेवा प्रदाता + शब्द + जानकारी + ध्वज + जन्मदिन + क्रमचर चिह्न + क्लिंक॰ प्रस्ताव + प्रदर्शन नाम + लैटनिंग जालपता + लैटनिंग पता + प्रयोक्तानाम + नोस्टर पता + चित्र + सर्वनाम + जालस्थान + अनुमत + अन्य क्रमक में परिवर्तित। %1$s + आपके कोंकोर्ड समुदाय आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया। + स्थानान्तरित + %1$s परित्यक्त + परित्यक्त + आपने उसे बनाया + आपने उसे परिवर्तित किया + आपने उसे रिक्त किया + सूचनावली + चला गया + विषयसूचक जिनका आप अनुगमन करते हैं + आगतपेटिका + जहाँ अन्य आप तक अभिगमन करते हैं + जुड गए + रखे गए + %1$s रखे गए + ग्राहक कुंचिका हटाया गया + ग्राहक कुंचिका प्रतिस्थापित + नटज्साप आगे से इस कुंचिका से बद्ध होंगे। यदि इस यन्त्र में वह नहीं है तो आप सम्भाव्यतः उनको यहाँ नहीं चुका सकते। + शेष + शेष + सूचीबद्ध + स्थानान्तरित + %1$s तथा %2$s अधिक + नया संस्करण + %1$s नए + नव्य + नव्य + नव्यतः बाधित + %1$s नए मौनकृत + अब से बाधित नहीं + स्थापित नहीं + यहाँ कुछ नहीं + बोल्ट॰बारह प्रस्ताव + + %1$d प्रस्ताव हटाया गया + %1$d प्रस्ताव हटाए गए + + निर्गतपेटिका + आप जहाँ प्रकाशित करते हैं + + आपको भुगतान करने की %1$d विधि हटाया गया + आपको भुगतान करने की %1$d विधियाँ हटाए गए + + लोग जो आपको भुगतान करते हैं उसी को देखते हैं जो इस सूची में है। + स्थल हटाए गए + स्थल जिनका आप अनुगमन करते हैं + वे अब रहस्यीकृत हैं। जो कि सम्भाव्यतः ऊपर के सार्वजनिक वस्तुओं का गन्तव्य था। + आपने वस्तुओं को निजी किया + वे रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। + आपने निजी वस्तुओं का परिवर्तन किया + वे रहस्यीकृत हैं। इसलिए केवल आप देख सकते हैं कि वे क्या थे। पुनःस्थापन से सब लौटके आ जाएँगे। + आपने सभी निजी वस्तुओं को हटा दिया + ग्रहण मध्य + पुनःनामकृत + पुनःनामकृत + इस यन्त्र पर + %1$s लोग खोजें + खोज मध्य + सेवा + अनुचर + श्रेणी + विषयसूची + %2$s में से %1$s सेवाएँ परिवर्तित + वे अब अन्य व्यक्ति से आते है। अथवा किसी से नहीं + अभी भी बाधित + अभिलेखन मध्य + परित्यक्त। %1$s + सम्पादित। %1$s + नए। %1$s + %1$s पारित + %1$s अद्यतनकृत + विषय हटाए गए + श्रेणीयाँ तथा अनुचर गिनती तथा कचरालेख छालन जो आप देखते हैं इन प्रदाताओं से आते हैं। + विश्वसनीय + %1$s अबाधित + अमेथिस्ट पुनः उनसे जुडेगा + इकाई + %1$s मौनावस्था हटाए गए + ये पुनः आपके सूचनावलियों तक अभिगम्य + अविश्वसनीय + आपके धनकोष के टकसाल तथा कुंचिकाएँ आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना वस्तु प्रति वस्तु यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया। + पूर्वतः %1$s था + शब्द तथा विषयसूचक + समचरणीकृत क्रमक स्थापना विकल्प + अमेथिस्ट स्थापना विकल्प समचरणीकृत आपके यन्त्रों में। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। + बाधित पुनःप्रसारक सूची + पुनःप्रसारक जिनसे यह क्रमक कभी भी संयोजन नहीं करता। + बोल्ट॰बारह प्रस्ताव + लैटनिंग प्रस्ताव जिनका उपयोग करके अन्य लोग आपको भुगतान करते हैं। + काशयू धनकोष + आपके ईकाश धनकोष स्थापना विकल्प तथा टकसाल। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। + समुदाय सूची + समुदाय जिनसे आप जुडे। + कोंकोर्ड समुदाय सूची + कोंकोर्ड समुदाय जिनसे आप जुडे। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है। + सीधासन्देश पुनःप्रसारक सूची + जहाँ अन्य आपके निजी सन्देश भेजते हैं। + अस्थायी चर्चा सूची + अस्थायी चर्चा शालाएँ जिनसे आप जुडे। + इष्ट सूचनावली सूची + कलनविध्यात्मक सूचनावलियाँ जिनको आपने अभिलेखित किया। + अनुचरण सूची + निर्णय करता है किसके पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। + अनुचरित स्थान + स्थान जहाँ से पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। + अनुचरित विषयसूचक + विषयसूचक जिससे युक्त पत्र आपकी मुख्य सूचनावली में प्रकट होंगे। + अनुक्रमणीकार पुनःप्रसारक सूची + पुनःप्रसारक उपयुक्त अन्य लोगों के परिचय तथा पुनःप्रसारक सूचियों को प्राप्त करने के लिए। + समूहचर्चा कुंचिका पुनःप्रसारक + जहाँ अन्य उन कुंचिकाओं को प्राप्त करते हैं आपको रहस्यीकृत समूहचर्चाओं में जोडने के लिए। + मौन सूची + छिपाता है लोग शब्द विषयसूचक तथा सूत्र उन सब में से जो आप देखते हैं। + नटज्साप प्राप्ती स्थापना विकल्प + टकसाल तथा कुंचिका जिनका उपयोग अन्य करते हैं आपको नटज्साप भेजने के लिए। + सूची + इस यन्त्र पर अभिलेखित सूची। + सार्वजनिक पुनःप्रसारक सूची + सबको बताता है आपके पत्र कहाँ प्राप्त करें तथा आपको उत्तर तथा उल्लेखों को कहाँ भेजें। + भुगतान पते + विधियाँ जिनसे अन्य लोग आपको भुगतान कर सके। + निजी पुनःप्रसारक सूची + जहाँ आपके पाण्डुलिपियाँ तथा निजी जानकारी रखे जाते हैं। + परिचय + आपका नाम चित्र चरित तथा भुगतान पते। जैसे सब को दिखते हैं वैसे। + सार्वजनिक चर्चा सूची + सार्वजनिक चर्चाएँ जिनसे आप जुडे। + इष्ट पुनःप्रसारक सूचनावलियाँ + पुनःप्रसारक जिनका आप वीक्षण करते हैं सूचनावलियों के रूप में। + समूह सूची + पुनःप्रसारक आधारित समूह जिनसे आप जुडे। + खोज पुनःप्रसारक सूची + उपयुक्त पुनःप्रसारक जब आप खोजते हैं। + विश्वाससेवा प्रदाता सूची + सेवाएँ जिनपर आप विश्वास करते है आपके लिए लोगों का सत्यापन तथा श्रेणीकरण करने के लिए। + विश्वसनीय पुनःप्रसारक सूची + पुनःप्रसारक जिनपर आप पर्याप्त विश्वास करते हैं बिना पूछे उनसे संयोजन करने के लिए। diff --git a/commonsUI/src/commonMain/composeResources/values-hu-rHU/strings.xml b/commonsUI/src/commonMain/composeResources/values-hu-rHU/strings.xml index 950cdfa8b9..4931f70f61 100644 --- a/commonsUI/src/commonMain/composeResources/values-hu-rHU/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-hu-rHU/strings.xml @@ -5287,4 +5287,322 @@ Aláírás nélküli, így ez a szerző csak állítás Egy %1$d típusú elem, amelyet ez a kliens nem jelenít meg %1$d bájt valamiből, amit ez a kliens nem olvas + %1$s újbóli letiltása + %1$s megtartása + Újbóli csatlakozás ehhez: %1$s + %1$s újbóli némítása + %1$s visszaállítása + Hírfolyamok visszaállítása + Helyek visszaállítása + Profil visszaállítása + Szolgáltatók visszaállítása + Átjátszók visszaállítása + Témák visszaállítása + Pénztárca visszaállítása + Elfogadás + + %1$s és %2$d hozzáadva + %1$s és %2$d hozzáadva + + + %1$s és %2$d letiltva + %1$s és %2$d letiltva + + + %1$s és %2$d követve + %1$s és %2$d követve + + + %1$s és %2$d csatlakozva + %1$s és %2$d csatlakozva + + + %1$s és %2$d némítva + %1$s és %2$d némítva + + A szinkronizált Amethyst alkalmazásbeállításai egy másik, nem Amethyst alkalmazásban módosultak. Ez problémákat okozhat. + Ön kiürítette a Concord listáját + Ön módosította a Concord listáját + + Ön %1$d privát üzenetátjátszót távolított el + %1$d privát üzenet átjátszót távolított el + + + Ön %1$d kedvenc hírfolyamot távolított el + %1$d kedvenc hírfolyamot távolított el + + + %1$d személyt nem követ többé + %1$d személyt nem követ többé + + + Ön %1$d kulcsszót nem követ többé + %1$d hashtaget nem követ többé + + + Ön %1$d indexelő átjátszót távolított el + %1$d indexelő átjátszót távolított el + + + Ön %1$d csoportos csevegési kulcsátjátszót távolított el + %1$d csoportcsevegés kulcs átjátszót távolított el + + + Ön %1$d nyilvános csevegést hagyott el + %1$d nyilvános csevegést hagyott el + + + Ön %1$d közösséget hagyott el + %1$d közösséget hagyott el + + + Ön %1$d csoportot hagyott el + %1$d csoportot hagyott el + + + Ön %1$d csevegőszobát hagyott el + %1$d csevegőszobát hagyott el + + + Ön %1$d dolog némítását oldotta fel + %1$d dolog némítását oldotta fel + + + Ön %1$d nyilvános átjátszót távolított el + %1$d nyilvános átjátszót távolított el + + Ön eltávolította a nutzap kulcsát + Ön lecserélte a nutzap kulcsát + + Ön %1$d nutzap pénzverdét távolított el + %1$d nutzap pénzverdét távolított el + + + Ön %1$d BOLT12 ajánlatot távolított el + %1$d BOLT12 ajánlatot távolított el + + + Ön %1$d fizetési módot távolított el + %1$d fizetési módot távolított el + + + Ön %1$d helyet nem követ többé + %1$d helyet nem követ többé + + + Ön %1$d privát átjátszót távolított el + %1$d privát átjátszót távolított el + + + Ön %1$d profilmezőt távolított el + %1$d profilmezőt távolított el + + + Ön %1$d átjátszó-hírfolyamot távolított el + %1$d átjátszó hírfolyamot távolított el + + + Ön %1$d kereső átjátszót távolított el + %1$d kereső átjátszót távolított el + + + Ön %1$d bizalmi szolgáltatás szolgáltatóját módosította + %1$d bizalmi szolgáltatás szolgáltatóját változtatta meg + + + Ön %1$d átjátszóban nem bízik többé + %1$d átjátszóban nem bízik többé + + + Ön %1$d átjátszó letiltását oldotta fel + %1$d átjátszó blokkolását oldotta fel + + Ön kiürítette a Cashu pénztárcáját + Ön módosította a Cashu pénztárcáját + Hozzáadva (%1$s) + Módosítva (%1$s) + %1$s hozzáadva + %1$s módosítva + %1$s eltávolítva + Új verzió használata + Ön privát (titkosított) elemeket adott hozzá. + Ön privát (titkosított) elemeket is módosított. Ezek itt nem hasonlíthatók össze. + Ön eltávolította az összes privát (titkosított) elemet. + csak olvasható + olvasás és írás + csak írható + Eltávolítva (%1$s) + Mentett verzió visszaállítása + Ön módosította a következőt: %1$s, egy másik alkalmazásban + Hírfolyamok + Csevegőszobák + Közösségek + Kulcsszavak + Kapcsolt identitások + Helyek + Pénzverdék + Nutzap kulcsok + Ajánlatok + Egyéb bejegyzések + Fizetési címek + Személyek + Profilmezők + Nyilvános csevegések + Átjátszók + Csoportok + Beszélgetések + Bizalmi szolgáltatók + Szavak + Névjegy + Borító + Születésnap + Bot jelölő + CLINK-ajánlat + Megjelenített név + LNURL + Lightning-cím + Felhasználónév + Nostr-cím + Kép + Névmások + Weboldal + Engedélyezve + Módosítva egy másik alkalmazásban · %1$s + A Concord közösségei Önhöz vannak titkosítva, így itt nem hasonlíthatók össze. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett. + Áthelyezve + %1$s elvetve + Elvetve + Ön hozta létre + Ön módosította + Ön kiürítette + Hírfolyam + Eltűnt + Ön által követett kulcsszavak + Bejövő + ahol mások elérik Önt + csatlakozott + megtartva + %1$s megtartva + Fogadási kulcs eltávolítva + Fogadási kulcs lecserélve + A nutzapok ehhez az új kulcshoz lesznek kötve. Ha ez az eszköz nem rendelkezik vele, lehet, hogy itt nem tudja beváltani őket. + elhagyta a csoportot + Elhagyva + Listázva + Áthelyezve + %1$s és %2$s további + Új verzió + %1$s új + új + Új + Újonnan letiltva + %1$s újonnan némítva + Már nincs letiltva + Nincs beállítva + Itt nincs semmi + BOLT12 ajánlat + + %1$d ajánlat eltávolítva + %1$d ajánlat eltávolítva + + Kimenő + ahol közzétesz + + %1$d fizetési mód eltávolítva + %1$d fizetési mód eltávolítva + + A fizető emberek csak azt látják, ami ebben a listában van. + eltűnt helyek + Ön által követett helyek + Most már titkosítva vannak, valószínűleg ide kerültek a fenti nyilvános elemek. + Ön priváttá tette az elemeket + Titkosítva vannak, így itt nem hasonlíthatók össze. + Ön módosította a privát elemeket + Titkosítva vannak, így csak Ön láthatja, mik voltak. A visszaállítás mindet visszahozza. + Ön eltávolította az összes privát elemet + Fogadás + átnevezte + Átnevezve + Ezen az eszközön + %1$s személy keresése + Keresés + Szolgáltatás + Követők + Rang + Témák + %2$s szolgáltatásból %1$s módosult + Most már valaki mástól vagy senkitől sem származnak + Továbbra is le van tiltva + Tárolás + Elvetve · %1$s + Szerkesztve · %1$s + Új · %1$s + %1$s törölve + %1$s frissítve + eltűnt témák + A látott rangok, követőszámok és a kéretlen tartalomszűrés ezektől a szolgáltatóktól származnak. + Megbízható + %1$s letiltása feloldva + Az Amethyst újra kapcsolódni fog hozzájuk + Egységek + %1$s némítása feloldva + Ezek újra megjelenhetnek a hírfolyamaiban + Nem megbízható + A saját pénztárca pénzverdéi és kulcsai Önhöz vannak titkosítva, így itt nem hasonlíthatók össze tételesen. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett. + „%1$s” volt + Szavak és kulcsszavak + szinkronizált alkalmazásbeállítások + Az eszközök között szinkronizált Amethyst-beállítások. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. + letiltott átjátszólista + Átjátszók, amelyekhez ez az alkalmazás soha nem kapcsolódik. + BOLT12 ajánlatok + A lightning ajánlatok, amelyeket mások a fizetéshez használnak az Ön számára. + Cashu pénztárca + Az ecash pénztárca beállításai és pénzverdéi. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. + közösséglista + Közösségek, amelyekhez csatlakozott. + Concord közösséglista + A Concord közösségek, amelyekhez csatlakozott. Titkosítva vannak, így csak a jelenlétük hasonlítható össze. + privát üzenet átjátszólista + Ahová mások kézbesítik a privát üzeneteit. + rövid életű csevegési lista + Rövid életű csevegőszobák, amelyekhez csatlakozott. + kedvenc hírfolyamlista + Mentett algoritmus-hírfolyamok. + követési lista + Meghatározza, kinek a bejegyzései jelenjenek meg a kezdőlapon. + követett helyek + Helyek, amelyek bejegyzései megjelennek a hírfolyamaiban. + követett kulcsszavak + Kulcsszavak, amelyek bejegyzései megjelennek a hírfolyamban. + indexelő átjátszólista + Azok az átjátszók, amelyeket mások profiljainak és átjátszólistáinak megtalálásához használnak. + csoportos csevegési kulcsátjátszók + Ahol mások megtalálják a kulcsokat, hogy titkosított csoportos csevegésekhez adhassák Önt. + némítási lista + Elrejti a személyeket, szavakat, kulcsszavakat és beszélgetéseket mindenből, amit lát. + nutzap fogadási beállítások + A pénzverdék és a kulcs, amelyeket mások a nutzapok küldéséhez használnak az Ön számára. + lista + Egy listaelem ezen az eszközön mentve. + nyilvános átjátszólista + Megmondja mindenkinek, hol találhatók a bejegyzései, és hová küldjék a válaszokat és említéseket. + fizetési címek + A módok, ahogy mások fizethetnek Önnek. + privát átjátszólista + Ahol a piszkozatai és privát adatai tárolódnak. + profil + Az Ön neve, képe, bemutatkozása és fizetési címei, ahogy mindenki látja őket. + nyilvános csevegési lista + Nyilvános csevegések, amelyekhez csatlakozott. + kedvenc átjátszóhírfolyamok + Olyan átjátszók, amelyeket hírfolyamként böngész. + csoportlista + Átjátszó-alapú csoportok, amelyekhez csatlakozott. + kereső átjátszólista + Kereséskor használt átjátszók. + bizalmi szolgáltatólista + A szolgáltatások, amelyekben megbízik, hogy rangsorolják és ellenőrizzék Önnek az embereket. + megbízható átjátszólista + Átjátszók, amelyekhez kérdezés nélkül kapcsolódhat. diff --git a/commonsUI/src/commonMain/composeResources/values-pl-rPL/strings.xml b/commonsUI/src/commonMain/composeResources/values-pl-rPL/strings.xml index 55a0998df3..6b3fd3bf9a 100644 --- a/commonsUI/src/commonMain/composeResources/values-pl-rPL/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-pl-rPL/strings.xml @@ -5507,4 +5507,382 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest Brak podpisu, więc autor to tylko domysł Element typu %1$d, którego ten klient nie wyświetla %1$d bajtów danych, których ten klient nie odczytuje + Zablokuj %1$s ponownie + Zachowaj %1$s + Ponów %1$s + Ponownie wycisz %1$s + Przywróć %1$s + Przywróć kanały + Przywróć miejsca + Przywróć profil + Przywróć dostawców + Przywróć transmitery + Przywróć tematy + Przywróć portfel + Zaakceptuj + + %1$s i dodano %2$d + %1$s i dodano %2$d + %1$s i dodano %2$d + %1$s i dodano %2$d + + + %1$s i zablokowano %2$d + %1$s i zablokowano %2$d + %1$s i zablokowano %2$d + %1$s i zablokowano %2$d + + + %1$s i obserwowany %2$d + %1$s i obserwowanych %2$d + %1$s i obserwowanych %2$d + %1$s i obserwowane %2$d + + + %1$s i dołączono do %2$d + %1$s i dołączono do %2$d + %1$s i dołączono do %2$d + %1$s i dołączono do %2$d + + + %1$s i wyciszony %2$d + %1$s i wyciszono %2$d + %1$s i wyciszono %2$d + %1$s i wyciszone %2$d + + Twoje zsynchronizowane ustawienia aplikacji Amethyst zostały zmienione w innej aplikacji, która nie jest Amethystem. To może powodować problemy. + Opróżniłeś swoją listę Concorda + Zmieniłeś swoją listę Concorda + + Usunąłeś %1$d transmiter DM + Usunąłeś %1$d transmiterów DM + Usunąłeś %1$d transmiterów DM + Usunąłeś %1$d transmitery DM + + + Usunąłeś %1$d ulubiony kanał + Usunąłeś %1$d ulubionych kanałów + Usunąłeś %1$d ulubionych kanałów + Usunięto %1$d ulubione kanały + + + Przestałeś obserwować %1$d osobę + Przestałeś obserwować %1$d osób + Przestałeś obserwować %1$d osób + Przestałeś obserwować %1$d osoby + + + Przestałeś obserwować %1$d hashtag + Przestałeś obserwować %1$d hashtagów + Przestałeś obserwować %1$d hashtagów + Przestałeś obserwować %1$d hashtagi + + + Usunięto %1$d transmiter indeksujący + Usunięto %1$d transmiterów indeksujących + Usunięto %1$d transmiterów indeksujących + Usunięto %1$d transmitery indeksujące + + + Usunięto %1$d transmiter klucza grupowego czatu + Usunięto %1$d transmiterów klucza grupowego czatu + Usunięto %1$d transmiterów klucza grupowego czatu + Usunięto %1$d transmitery klucza grupowego czatu + + + Opuściłeś %1$d czat publiczny + Opuściłeś %1$d czatów publicznych + Opuściłeś %1$d czatów publicznych + Opuściłeś %1$d czaty publiczne + + + Opuściłeś %1$d społeczność + Opuściłeś %1$d społeczności + Opuściłeś %1$d społeczności + Opuściłeś %1$d społeczności + + + Opuściłeś %1$d grupę + Opuściłeś %1$d grup + Opuściłeś %1$d grup + Opuszczono %1$d grupy + + + Opuściłeś %1$d pokój czatu + Opuściłeś %1$d pokoi czatu + Opuściłeś %1$d pokoi czatu + Opuściłeś %1$d pokoje czatu + + + Włączyłeś dźwięk dla %1$d elementu + Włączyłeś dźwięk dla %1$d elementów + Włączyłeś dźwięk dla %1$d elementów + %1$d elementy mają włączony dźwięk + + + Usunąłeś %1$d publiczny transmiter + Usunąłeś %1$d publicznych transmiterów + Usunąłeś %1$d publicznych transmiterów + Usunąłeś %1$d publiczne transmitery + + Usunąłeś swój klucz nutzap + Wymieniłeś swój klucz Nutzap + + Usunięto %1$d nutzap mint + Usunięto %1$d nutzap mintów + Usunięto %1$d nutzap mintów + Usunięto %1$d nutzap minty + + + Usunięto %1$d ofertę BOLT12 + Usunięto %1$d ofert BOLT12 + Usunięto %1$d ofert BOLT12 + Usunięto %1$d oferty BOLT12 + + + Usunięto %1$d sposób na otrzymanie zapłaty + Usunięto %1$d sposobów otrzymywania zapłaty + Usunięto %1$d sposobów otrzymywania zapłaty + Usunięto %1$d sposoby otrzymywania zapłaty + + + Przestałeś obserwować %1$d miejsce + Przestałeś obserwować %1$d miejsc + Przestałeś obserwować %1$d miejsc + Przestałeś obserwować %1$d miejsca + + + Usunięto %1$d prywatny transmiter + Usunięto %1$d prywatnych transmiterów + Usunięto %1$d prywatnych transmiterów + Usunięto %1$d prywatne transmitery + + + Usunąłeś %1$d pole profilu + Usunąłeś %1$d pól profilowych + Usunąłeś %1$d pól profilowych + Usunąłeś %1$d pola profilowe + + + Usunięto %1$d kanał transmitera + Usunięto %1$d kanałów transmitera + Usunięto %1$d kanałów transmitera + Usunięto %1$d kanały transmitera + + + Usunięto %1$d transmiter wyszukiwania + Usunięto %1$d transmiterów wyszukiwania + Usunięto %1$d transmiterów wyszukiwania + Usunięto %1$d transmitery wyszukiwania + + + Zmieniłeś dostawcę %1$d zaufanego serwisu + Zmieniłeś dostawcę %1$d zaufanych serwisów + Zmieniłeś dostawcę %1$d zaufanych serwisów + Zmieniłeś dostawcę %1$d zaufanych serwisów + + + Przestałeś ufać %1$d transmiterowi + Przestałeś ufać %1$d transmiterom + Przestałeś ufać %1$d transmiterom + Przestałeś ufać %1$d transmiterom + + + Odblokowałeś %1$d transmiter + Odblokowałeś %1$d transmiterów + Odblokowałeś %1$d transmiterów + Odblokowałeś %1$d transmitery + + Opróżniłeś swój portfel Cashu + Zmieniłeś swój portfel Cashu + Dodano (%1$s) + Zmieniono (%1$s) + Dodano %1$s + %1$s zmieniono + Usunięto %1$s + Użyj nowej wersji + Dodano prywatne (zaszyfrowane) pozycje. + Zmieniłeś również elementy prywatne (zaszyfrowane). Nie można ich tutaj porównać. + Usunąłeś wszystkie prywatne (zaszyfrowane) elementy. + tylko do odczytu + odczyt i zapis + tylko do zapisu + Usunięto (%1$s) + Przywróć zapisaną wersję + Zmieniłeś %1$s w innej aplikacji + Kanały + Czat pokoje + Społeczności + Hashtagi + Powiązane tożsamości + Lokalizacje + Minty + Klucze Nutzap + Oferty + Inne wpisy + Adresy płatności + Osoby + Pola profilu + Czaty publiczne + Transmitery + Grupy + Wątki + Zaufani dostawcy + Słowa + O profilu + Baner + Data urodzin + Flaga bota + Oferta CLINK + Nazwa wyświetlana + LNURL + Lightning Adres + Nazwa użytkownika + Adres Nostr + Zdjęcie + Zaimki + Strona www + Dozwolona + Zmieniono w innej aplikacji · %1$s + Twoje społeczności w Concord są dla Ciebie zaszyfrowane, więc nie można ich tutaj porównać. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu. + Przeniesiona + %1$s odrzuconych + Usunięta + Utworzyłeś to + Zmieniłeś to + Opróżniłeś to + Kanał + Zniknęła + Hashtagi, które obserwujesz + Skrzynka odbiorcza + gdzie inni docierają do Ciebie + dołączył(a) + zachowane + Zachowano %1$s + Usunięto klucz odbierający + Wymieniono klucz odbierający + Nutzapy zostaną przypisane do tego nowego klucza. Jeśli urządzenie nie posiada tego klucza, może się okazać, że nie będzie można ich tutaj zrealizować. + zostało + Zostało + Na Liście + Przeniesiona + %1$s i %2$s więcej + Nowa wersja + %1$s nowych + nowe + Nowa + Nowo zablokowana + %1$s nowo wyciszonych + Nie jest już zablokowana + Nie ustawiono + Nic tu nie ma! + Oferta BOLT12 + + %1$d oferta usunięta + %1$d ofert usunięto + %1$d ofert usunięto + %1$d oferty usunięte + + Skrzynka nadawcza + gdzie publikujesz + + %1$d sposób płatności został usunięty + %1$d sposobów płatności zostało usuniętych + %1$d sposobów płatności zostało usuniętych + %1$d sposoby płatności zostały usunięte + + Osoby, które Ci płacą, widzą tylko to, co znajduje się na tej liście. + miejsca zniknęły + Miejsca które obserwujesz + Są teraz zaszyfrowane, co prawdopodobnie wyjaśnia, gdzie podziały się powyższe elementy publiczne. + Ustawiłeś te pozycje jako prywatne + Są zaszyfrowane, więc nie można ich tu porównać. + Zmieniłeś prywatne elementy + Są zaszyfrowane, więc tylko Ty możesz zobaczyć, co to było. Odtworzenie przywróci je wszystkie. + Usunąłeś wszystkie prywatne elementy + Pobieranie + zmieniono nazwę + Zmieniono nazwę + Na tym urządzeniu + Szukaj %1$s osób + Szukam + Serwis + Obserwujący + Ranking + Tematy + Zmieniono %1$s z %2$s serwisów + Teraz pochodzą od kogoś innego albo od nikogo + Nadal zablokowana + Przechowywanie + Usunięte · %1$s + Edytowane · %1$s + Nowe · %1$s + %1$s wyczyszczono + %1$s zaktualizowano + tematy zniknęły + Rankingi, liczba obserwujących i filtrowanie spamu, które widzisz, pochodzą od tych dostawców. + Zaufane + %1$s odblokowane + Ametyst ponownie połączy się z nimi + Jednostek + %1$s odblokowanych + Mogą one ponownie pojawić się w Twoich informacjach + Niezaufane + Minty i klucze w Twoim portfelu są zaszyfrowane dla Ciebie, więc nie można ich tutaj porównać pozycja po pozycji. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu. + było „%1$s” + Wyrazy & hasztagi + zsynchronizowane ustawienia aplikacji + Ustawienia Amethyst są synchronizowane na wszystkich Twoich urządzeniach. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich obecności. + lista zablokowanych przekaźników + Transmitery, z którymi ta aplikacja nigdy się nie łączy. + Oferta BOLT12 + Ta możliwość pozwala innym płacić Ci za pomocą Lightning. + Portfel Cashu + Ustawienia portfela ecash oraz minty. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich istnienia. + lista społeczności + Społeczności, do których dołączyłeś. + Lista społeczności Concord + Społeczności Concord, do których dołączyłeś. Są one zaszyfrowane, więc można porównać jedynie fakt ich istnienia. + Lista transmiterów DM + Gdzie inni dostarczają Twoje prywatne wiadomości. + lista czatów efemerycznych + Tymczasowe czaty, do których dołączyłeś. + lista ulubionych kanałów + Zapisane przez Ciebie kanały algorytmiczne. + lista obserwowanych + Określa, czyje posty pojawiają się w Twoim kanale głównym. + obserwowane lokalizacje + Miejsca, z których posty pojawiają się na Twoich kanałach. + obserwowane hashtagi + Hashtagi, z których pochodzą posty wyświetlane w Twoich kanałach. + lista transmiterów indeksujących + Transmitery służące do wyszukiwania profili innych osób oraz list transmiterów. + transmitery kluczy czatu grupowego + Gdzie inni znajdą klucze do dodawania cię do zaszyfrowanych czatów grupowych. + lista zablokowanych + Ukrywa osoby, słowa, hashtagi i wątki we wszystkich wyświetlanych treściach. + ustawienia odbioru nutzap + Minty i klucz, które inni używają do wysyłania ci „nutzapów”. + lista + Lista zapisana na tym urządzeniu. + lista publicznych transmiterów + Informuje wszystkich, gdzie mogą znaleźć Twoje posty oraz gdzie mają wysyłać Ci odpowiedzi i wzmianki. + adresy płatności + Sposoby, w jakie inni mogą Ci zapłacić. + lista prywatnych transmiterów + Gdzie przechowywane są Twoje projekty i prywatne dane. + profil + Twoje imię i nazwisko, zdjęcie, biografia oraz adresy do płatności – tak, jak widzą je wszyscy. + lista czatów publicznych + Publiczne czaty, do których dołączyłeś. + ulubione kanały transmitera + Transmitery, które przeglądasz jako kanały informacyjne. + lista grup + Grupy oparte na transmiterach, do których dołączyłeś. + lista transmiterów wyszukiwania + Transmitery używane podczas wyszukiwania. + lista zaufanych dostawców + Serwisy, którym ufasz, a które zajmują się klasyfikowaniem i weryfikacją osób w Twoim imieniu. + lista zaufanych transmiterów + Zaufane transmitery, z którymi bez wahania nawiązujesz połączenie. diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index a0b587b986..f8a7c986c4 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -5482,4 +5482,574 @@ Unsigned, so this author is a claim An item of kind %1$d, which this client does not draw %1$d bytes of something this client does not read + Block %1$s again + Keep %1$s + Rejoin %1$s + Re-mute %1$s + Restore %1$s + Restore feeds + Restore places + Restore profile + Restore providers + Restore relays + Restore topics + Restore wallet + Accept + + %1$s and added %2$d + %1$s and added %2$d + + + %1$s and blocked %2$d + %1$s and blocked %2$d + + + %1$s and followed %2$d + %1$s and followed %2$d + + + %1$s and joined %2$d + %1$s and joined %2$d + + + %1$s and muted %2$d + %1$s and muted %2$d + + Your Amethyst synced app settings changed in another app that is not Amethyst. This can create problems. + You emptied your Concord list + You changed your Concord list + + You removed %1$d DM relay + You removed %1$d DM relays + + + You removed %1$d favorite feed + You removed %1$d favorite feeds + + + You unfollowed %1$d person + You unfollowed %1$d people + + + You unfollowed %1$d hashtag + You unfollowed %1$d hashtags + + + You removed %1$d indexer relay + You removed %1$d indexer relays + + + You removed %1$d group-chat key relay + You removed %1$d group-chat key relays + + + You left %1$d public chat + You left %1$d public chats + + + You left %1$d community + You left %1$d communities + + + You left %1$d group + You left %1$d groups + + + You left %1$d chat room + You left %1$d chat rooms + + + You unmuted %1$d thing + You unmuted %1$d things + + + You removed %1$d public relay + You removed %1$d public relays + + You removed your nutzap key + You replaced your nutzap key + + You removed %1$d nutzap mint + You removed %1$d nutzap mints + + + You removed %1$d BOLT12 offer + You removed %1$d BOLT12 offers + + + You removed %1$d way to get paid + You removed %1$d ways to get paid + + + You unfollowed %1$d place + You unfollowed %1$d places + + + You removed %1$d private relay + You removed %1$d private relays + + + You removed %1$d profile field + You removed %1$d profile fields + + + You removed %1$d relay feed + You removed %1$d relay feeds + + + You removed %1$d search relay + You removed %1$d search relays + + + You changed the provider of %1$d trust service + You changed the provider of %1$d trust services + + + You stopped trusting %1$d relay + You stopped trusting %1$d relays + + + You unblocked %1$d relay + You unblocked %1$d relays + + You emptied your Cashu wallet + You changed your Cashu wallet + Added (%1$s) + Changed (%1$s) + %1$s added + %1$s changed + %1$s removed + Use new version + You added private (encrypted) items. + You also changed private (encrypted) items. They can't be compared here. + You removed all private (encrypted) items. + read only + read and write + write only + Removed (%1$s) + Restore saved version + You changed your %1$s in another app + Feeds + Chat rooms + Communities + Hashtags + Linked identities + Locations + Mints + Nutzap keys + Offers + Other entries + Payment addresses + People + Profile fields + Public chats + Relays + Groups + Threads + Trust providers + Words + About + Banner + Birthday + Bot flag + CLINK offer + Display name + LNURL + Lightning address + Username + Nostr address + Picture + Pronouns + Website + Allowed + Changed in another app · %1$s + Your Concord communities are encrypted to you, so they can't be compared here. Restoring puts back exactly what this device saved. + Moved + %1$s dropped + Dropped + You created it + You changed it + You emptied it + Feed + Gone + Hashtags you follow + Inbox + where others reach you + joined + kept + %1$s kept + Receiving key removed + Receiving key replaced + Nutzaps will be locked to this new key. If this device doesn't have it, you may not be able to redeem them here. + left + Left + Listed + Moved + %1$s and %2$s more + New version + %1$s new + new + New + Newly blocked + %1$s newly muted + No longer blocked + Not set + Nothing here + BOLT12 offer + + %1$d offer removed + %1$d offers removed + + Outbox + where you publish + + %1$d way to pay you removed + %1$d ways to pay you removed + + People paying you only see what's in this list. + places gone + Places you follow + They're encrypted now, which is likely where the public items above went. + You made items private + They're encrypted, so they can't be compared here. + You changed private items + They're encrypted, so only you can see what they were. Restoring brings them all back. + You removed all private items + Receiving + renamed + Renamed + On this device + Search %1$s people + Searching + Service + Followers + Rank + Topics + %1$s of %2$s services changed + They now come from someone else, or no one + Still blocked + Storing + Dropped · %1$s + Edited · %1$s + New · %1$s + %1$s cleared + %1$s updated + topics gone + Ranks, follower counts and spam filtering you see come from these providers. + Trusted + %1$s unblocked + Amethyst will connect to them again + Units + %1$s unmuted + These can reach your feeds again + Untrusted + Your wallet's mints and keys are encrypted to you, so they can't be compared item by item here. Restoring puts back exactly what this device saved. + was “%1$s” + Words & hashtags + synced app settings + Amethyst settings synced across your devices. They are encrypted, so only their presence can be compared. + blocked relay list + Relays this app never connects to. + BOLT12 offers + The lightning offers others use to pay you. + Cashu wallet + Your ecash wallet settings and mints. They are encrypted, so only their presence can be compared. + community list + The communities you joined. + Concord community list + The Concord communities you joined. They are encrypted, so only their presence can be compared. + DM relay list + Where others deliver your private messages. + ephemeral chat list + The ephemeral chat rooms you joined. + favorite feeds list + The algorithmic feeds you saved. + follow list + Decides whose posts appear in your home feed. + followed locations + Locations whose posts appear in your feeds. + followed hashtags + Hashtags whose posts appear in your feeds. + indexer relay list + The relays used to find other people's profiles and relay lists. + group-chat key relays + Where others find the keys to add you to encrypted group chats. + mute list + Hides people, words, hashtags and threads from everything you see. + nutzap receiving settings + The mints and key others use to send you nutzaps. + list + A list saved on this device. + public relay list + Tells everyone where to find your posts and where to send you replies and mentions. + payment addresses + The ways other people can pay you. + private relay list + Where your drafts and private data are stored. + profile + Your name, picture, bio and payment addresses, as everyone sees them. + public chat list + The public chats you joined. + favorite relay feeds + The relays you browse as feeds. + group list + The relay-based groups you joined. + search relay list + The relays used when you search. + trust provider list + The services you trust to rank and verify people for you. + trusted relay list + Relays you trust to connect to without asking. + Delete + Edit + Editing your message + Go to message + Pin + Reply + Unpin + That change could not be made. + The coordinator did not answer, so nothing was changed. Try again in a moment. + %1$s has not published a key package to this coordinator, so they cannot be added yet. Ask them to open cordn on this coordinator first. + You cannot remove yourself from a cordn group. + %1$s could not be given a way into the group, so the add was abandoned. + %1$s is not in this group. + That person + This coordinator served a key package that belongs to someone else, so %1$s was not added. Nothing was changed. + Your group encryption keys, your key packages, and every message in those groups. Anyone who opens it can read the conversations. The passphrase is the only thing protecting it once it leaves Amethyst. + What is in the file + Your nsec cannot bring cordn groups back. A cordn group lives as encryption state on this device and an ordered stream on a coordinator that cannot read it — lose the device without a backup and the group is gone, including everything already said in it. + Export + Backup saved. + That did not work. Check the passphrase and the file. + Passphrase + Restore + Restoring replaces the cordn groups on this device with the ones in the file. It is for a phone that has taken over from another — not a way to use the same groups on two devices at once. + Every cordn group now on this device will be removed and replaced by the ones in the file.\n\nDo not restore onto a second phone while the first is still in use. Both would hold the same group keys, and once both have sent a message the group breaks for everyone — silently, and with no way back. + Replace this device's cordn groups? + Restore + Restored. + Anything sent here is encrypted for this group. The coordinator relays it without being able to read it. + No messages yet + New messages + Yesterday + Message + Add + Could not open that coordinator. + Copy the coordinator's npub + Look for coordinators + Add + Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked. + Could not read announcements + Nobody is announcing on your relays. + Announced %1$s ago + Last announced %1$s + Nothing found, and %1$d of your relays did not answer. + A coordinator orders one group's messages and is the only thing that can. Losing it loses the ordering, and a second one does not mirror the first — so these are named things you choose, not interchangeable relays. + Not answering — %1$d calls in a row have failed. Groups on it are not syncing. + Answering. + A call failed; retrying. + Nothing asked of it yet. + Hide the ones that stopped announcing + Ask who it is + Coordinator key + A name for it (yours, optional) + This name is yours and stays on this device. A coordinator cannot prove a name, so it is never asked for one. + More actions + No coordinators yet. + Purge + Removing keeps the groups on this device, so adding the coordinator back brings them with it. Purging deletes them: the group keys, the read positions and your key packages all go, and those groups can only be re-entered by a fresh invitation.\n\nThe coordinator is not told and keeps whatever it already had. + Purge this coordinator? + %1$s + Answers on + %1$s +%2$d more + Remove + Rename + Says it is: %1$s + A coordinator's name and version are strings it chose. Its public key is the only thing that identifies it. + Answered, but named nothing. + Show all %1$d + Show fewer + Show %1$d that stopped announcing + These have not announced in over a month. Creating a group on one that has gone away will fail. + Create group + + Create and invite %1$d person + Create and invite %1$d people + + Add someone + Admin + + %1$d person can add and remove + %1$d people can add and remove + + Only I manage this group + Only you can invite, remove members or change the group's details. You can hand that to someone else later. + Coordinator + Change + Find coordinators + Done + Another coordinator + Coordinator public key (hex or npub) + Relays it answers on, one per line + Reaches all %1$d of your people + Reaches %1$d of %2$d — the rest would be left out + Not checked — picking it will ask + Description (optional) + Everyone can add and remove + Everyone in the group can invite, remove members and change its details. Any member can name admins later, which ends that. + A cordn group is ordered by one coordinator you choose. Creating the group happens on this device — the coordinator only learns the group exists once you invite someone or send a message. + The group could not be created. + Group name + No coordinator chosen + Each invitation is a Welcome left on the coordinator, so they can fail one at a time. Nobody has been notified: they see it next time they open their app. + The coordinator refused the invitation + Send a link + Added to the group, but the invitation did not reach them — try again from the group screen + Open the chat + No key here, so no Welcome could be left + The group exists + Waiting for them + Done + Nobody added yet. A group can be created empty and invitations sent later, but naming people now is what shows which coordinator can reach them. + Nobody yet — tap to add people + Naming people now is what tells us which coordinator can reach them. Each one gets a Welcome left on that coordinator, which they pick up next time they open their app. + + Reachable on %1$d coordinator + Reachable on %1$d coordinators + + No key on any coordinator you use + Not checked yet + Who can add and remove + Name it + Who is in it + Where it lives + New Cordn group + + %1$d person cannot be reached by any coordinator you use + %1$d people cannot be reached by any coordinator you use + + You, always an admin + Everyone in the group will see this message as withdrawn, and Amethyst will stop showing its text. The coordinator still holds the encrypted copy it was sent as, and anyone who already read it has already read it. + Withdraw message + Accepted by the coordinator + Got it + What this coordinator can see + Group info + This group is not available. Its coordinator may have been removed. + Add a coordinator in Settings to start using cordn groups. + + %1$d group + %1$d groups + + Nothing is arriving from these groups. Amethyst keeps retrying. + Coordinators + No cordn groups yet + A cordn group lives on one coordinator that holds its membership and passes messages along, without ever seeing what anyone writes. + Start one + Add someone + Nobody found by that name. + They can only be added if they published a key package to this coordinator. + Name, npub or name@domain + Admin + + The group's %1$d admin stays as it is. + The group's %1$d admins stay as they are. + + Coordinator + Coordinator key + Coordinator link + Copy the coordinator's nprofile + Edit details + This group has no admins: everyone can add, remove and rename, permanently. + Epoch + Group id + Can be added on this coordinator + Members + %1$s stops receiving messages from here. They keep what they have already read, and this cannot be undone — they would have to be invited again. + Remove from group? + Remove + Save + Technical details + Join + Decline + Declining is permanent. Getting back in means being invited again. + Checked when you open this screen, and at no other time — asking a coordinator whether anyone invited you tells it you are here, so Amethyst does not do it in the background. + Could not read invitations. + + %1$d person is already in this group + %1$d people are already in this group + + +%1$d more + You have no coordinator open. cordn invitations arrive through a coordinator, so there is nowhere to look yet. + No invitations waiting. + Check again + An invitation was left for another device + Cordn invitations + %1$s did not answer + Through %1$s + Publishing signs a record under your own account key. That coordinator then knows this account exists and is invitable — permanently, and whether or not anyone invites you. + Others can only add you to a cordn group by taking a key package you published to that coordinator. cordn has no other place to keep one, so with none published nobody can invite you — and nothing tells them why. + Could not read the key packages. + No last-resort package. Once the single-use ones run out, invitations fail. + A last-resort package is published, so an invitation can still be made once the single-use ones run out. + + %1$d package this device cannot open + %1$d packages this device cannot open + + The coordinator will hand these to anyone inviting you, but the private half is not on this device — it belongs to another install. If that install is gone, an invitation using one of these produces a welcome nobody can ever open. + Publish one + Publish last-resort + + %1$d single-use package available. + %1$d single-use packages available. + + Once you have published here, Amethyst quietly replaces packages as they are used up. It never publishes the first one for you. + Withdraw all + Withdraw those + Ask to join this group + This publishes a key package under your own account key and tells the coordinator you want into this group — whether or not anyone answers. + Could not ask to join. + Asked. A member of the group has to add you; the invitation will show up under cordn invitations. + Scan + Could not open that file. + No media server is set for this account, so there is nowhere to put the file. Pick one in Settings. + That file could not be read. + Could not send that file. + + %1$d member + %1$d members + + Message deleted + Coordinator + Cursor + Sent + Message details + edited + Pinned by %1$s + + %1$d pinned message in this group + %1$d pinned messages in this group + + Next pinned message + Previous pinned message + Show all pinned messages + Pinned messages + Reactions + Add + Only an admin can accept. In a group with no admins, that is everyone. + Check for requests + Dismiss + Could not read the requests. + Nobody is waiting to join. + Requests to join + Send + That did not send. + This group's coordinator is not open, so nothing can be sent yet. + Copied + Copy link + Anyone with this link can ask to join. An admin still has to let them in. + Share this group + Play voice note + Record a voice note + Stop and send