Merging keepActiveUser into apiCreateActiveUser changed a signature that two Kotlin
docs quote. spec/api.md still described the old parameters and said the command sets
the profile as active, full stop; product/flows/onboarding.md quoted the old signature
one step above the line this branch had already updated to mention the flag. The iOS
spec table was fixed in the previous commit and these were missed - the same drift on
the other platform.
Both iOS call sites now use listUsersAsync, which is what changeActiveUserAsync_ uses
a few lines later; the synchronous listUsers blocks whatever executor the Task is on,
and SwiftUI infers that as the main actor.
Not compiled: the Kotlin changes here are markdown only, and the Swift ones cannot be
built on this machine.
Correctness.
The one-time link picker's profile list was keyed on the active user as well as the
count. That defeats the "don't change order after a user was selected" behaviour its own
comment protects - changeActiveUser_ sets currentUser first and only then reloads chats,
so the list visibly re-sorted and stayed re-sorted for the whole of getUserChatData - and
it was redundant: the count alone covers a profile being created from the picker.
iOS, compose picker: the old-core fallback relied on the user switch tearing this view
down, which only happens when the switch succeeded. If it threw, the form was left open
over a profile that already existed, and the only way out was to swipe it away. It is now
dismissed unconditionally.
iOS: alerts raised right after a sheet is dismissed are presented on a controller that is
going away and are dropped. The 0.5s wait was applied only to the old-core path, while the
ordinary reassignment failures - the ones that actually happen - went unguarded.
alertAfterDismissal now covers all of them, in one place.
iOS: dropped the chatId write that Kotlin removed for the same reason two commits ago. It
is a no-op on the happy path, but not if something legitimately closed the chat meanwhile
- SimpleX lock re-auth clears it, and this would reopen the chat behind the lock screen.
iOS: the create-profile row is now gated on profileChangeProhibited, as every other row on
both platforms already was.
Surface.
apiCreateActiveUser takes keepActiveUser as a defaulted argument, exactly as it already
takes pastTimestamp, instead of a public wrapper plus a private helper. Two symbols fewer
on each platform, and every existing call site passes its arguments by name, so none of
them changes.
Reverted the activeOrder sort flip in the compose picker: it changes the order of an
existing screen for every user, is not needed for this feature, and with active_order 0
would push a newly created profile to the bottom. It deserves its own commit if wanted.
Also: apps/ios/spec/api.md tracks the command enum and had gone stale; the plan records
the two limits of the flag the review surfaced - it is ignored when there is no active
user, and active_order 0 ties rather than sorts last on migrated databases.
No behaviour change. The review rounds left the flow carrying their own reasoning:
267 of the 759 added lines were comments, and the fixes had accreted structure that
was no longer needed.
createProfileForInvitation runs on Main via withApi, as the pickers' own handlers
always did, instead of withBGApi with three withContext(Dispatchers.Main) blocks
inside it. Every call it makes suspends into IO - sendCmd hops there itself - so Main
is never blocked, and the whole class of off-main model mutation that two rounds of
review found simply cannot arise. That removes the two coroutine imports as well.
hasModalOpenNotClosing is gone: while the form is open it is necessarily the last
modal on the fullscreen stack, so the entry guard uses isLastModalOpenNotClosing and
one function covers both call sites.
The rest is comments. Kept the ones that record why something non-obvious is the way
it is - the placement, the main-thread choice, the guards - and dropped the narration
of what the earlier attempts got wrong, which belongs in the history and the plan.
One comment described code that is not there at all; it is the commit message's job
to say why the chatId write was removed.
759 -> 667 added lines, 267 -> 189 of them comments.
MainActor.run's body is @Sendable, and the switched flag added in the previous commit
was a var captured by it - which strict concurrency diagnoses. Assigning a let on both
branches of the do/catch is definitely initialised and safe to capture anywhere, with
no behaviour change.
Third review pass. Still not compiled - no Swift toolchain here.
The compose picker's fallback had moved the chatId clear out of the do block, so it
fired when the switch had FAILED - closing a prepared chat that was still perfectly
valid and still owned by the active profile. It is now conditional on the switch having
happened.
Dismissing the form before the switch did not help either: the dismissal animates for
about a third of a second, getTopViewController() keeps returning the sheet until it
ends, and the local database work finishes sooner - so the alert was presented on a
controller being dismissed and dropped, on the common path. The explicit dismissal is
gone (the switch removes this view and its sheet anyway) and the alert is deferred past
the transition.
The one-time link picker's rows had no guard at all: allowsHitTesting keyed on
switchingProfileByTimeout, which latches half a second late, so a second row was
tappable for that whole window right after the form closed - starting a second
connection change on a pccConnId the first was recreating. Both pickers now block input
from the moment the work starts.
In the compose picker the guard is on the branches that start work, not on the whole
Button, so expanding and collapsing the list - which is local - still works, and a tap
in a chat where the profile cannot be changed still explains itself.
Also: that picker's other-profiles list sorted activeOrder ascending while every other
list on both platforms sorts descending, which the core change made visible by giving a
never-activated profile order 0; the one-time link picker's fallback now dismisses and
resyncs its selection as the compose picker does; and the incognito handler's new else
no longer writes incognitoEnabled, which is bound to the app-wide default and marks the
invitation as used.
Third review pass on this flow. Four things, all in code the previous two passes
touched.
The one-time link picker's close(). selectProfileAsync ended with an unconditional
close(), which pops whatever is on top of the start stack. The reassignment before it
can wait indefinitely on the retry alert, and back is not blocked while it does - so
backing out and then letting the call complete dismissed the New chat screen as well.
The picker now has its own ModalViewId and only closes if it is still the top, which is
what isLastModalOpenNotClosing was added for; the create-profile flow was using it and
this, the older hazard, was not.
The entry guard used hasModalOpen, which counts a modal that is still animating out, so
re-opening the form straight after backing out of it was a silent no-op for the length
of the animation - the exact bug the NotClosing variant exists for. Adds the matching
hasModalOpenNotClosing.
The old-core fallback switched the active user and returned without closing the form,
leaving it on top of a chat list belonging to a different profile, and did it off the
main thread. It now closes first and runs on Main like the rest of the flow.
chatModel.chatId is no longer written after the switch. changeActiveUser_ passes
keepingChatId and updateChats only clears chatId when the chat is missing from the
reloaded list, so on success the write was a no-op - and in the one case the guard does
fire it re-pointed the chat view at a chat that is not there. It also undid a
notification tap that had navigated to another chat of the same profile, which the
active-user check cannot catch.
Also restores the profileChangeProhibited guard that the previous commit dropped from
the new row while collapsing its click handler; keys the one-time link picker's profile
list on the active user as well as the count, so the checkmark is not stale after a
switch that leaves the count unchanged; dims the compose picker while it is busy, which
was the only picker with no indication at all; and logs the silent abort.
The /users assertion added with the active_order fix does not test it: viewUsersList
sorts alphabetically by display name, so "bob" precedes "robert" whatever their order
is. What the assertion does prove is that the created user's active_user column is 0,
which /u does not cover - the comment now says that and nothing more. The ordering
itself has no regression coverage; the sort sites are all in the clients.
The plan claimed the picker's composition is disposed on Android while the form is on
top. That is true of the one-time link picker, which is itself a modal, and false of
the compose picker, which is not. It also recorded the pane-per-picker placement that
this round replaced with fullscreen.
Follow-up to the previous iOS commit. Still not compiled - no Swift toolchain here.
The "core ignored keepActiveUser" path in the compose picker rested on a false
premise. It claimed the form stays open and the alert draws over it, but the resync it
performs replaces the chat list with the new profile's, and the prepared chat stayed
with the previous one - so getChat returns nothing, the pushed chat view renders
nothing, and this picker, which is the sheet's presenter, is removed by the switch
itself. The alert was then raised into a hierarchy being torn down and discarded. The
form is now dismissed first, and the chat id cleared only if it is still the chat this
picker belongs to, since a notification tap may have navigated elsewhere meanwhile.
Rows stay live during the reassignment. creatingProfile is cleared as soon as
createProfileForChat returns, which is the moment it hands off to changeProfile's
fire-and-forget Task, and none of the rows had a disabled state at all - so a second
change could be started on top of the first, two concurrent reassignments of the same
prepared contact and two racing user switches. This is what the Kotlin side made
onCreated suspending for.
The one-time link picker's new else branch reset the switch status but not the
selection, so after a cancelled retry the profile that was not switched to kept the
checkmark and tapping it did nothing - the view thought it was already selected. Its
"core ignored the flag" path likewise left the picker pointing at a profile that is no
longer active. The incognito handler has the identical latch the else branch was added
for, and hardening the row's disabled state on profileSwitchStatus made that stuck path
disable the row permanently, so it gets the same treatment - and its two state writes
move onto the main actor, where the rest of that block already was.
Both swallowed errors are now logged, and the comment claiming two .sheet modifiers on
one view conflict is dropped - it contradicted the sibling file and the deployment
target.
Follow-up to the previous commit, which fixed the centre-pane placement by moving
the form to the picker's own pane. That was wrong in two different ways, and the
shared modal helpers it changed had a blast radius that was not accounted for.
Placement. ModalManager.end, used for the compose picker, puts the form in a pane
*beside* the chat: the picker stays fully live next to it, with no scrim, so a profile
can be selected while the form is open - and apiChangeConnectionUser recreates the
connection, invalidating the id the form is about to use. It also calls
desktopExpandWindowToWidth, which is grow-only, so the window is permanently widened.
ModalManager.start, used for the one-time link picker, does cover it, but disposes it,
losing whatever was typed in its search box. ModalManager.fullscreen is an opaque
Surface over every pane: nothing can be operated while the form is up, nothing is torn
down, chatId is untouched and the window does not move. The manager is no longer a
parameter - there is only one right answer.
Modal helpers. hasModalOpen/isLastModalOpen were taught to ignore modals waiting out
their close animation, but they have five other callers - ChatListView, ChatView twice,
ChatItemView and SimpleXAPI - all deciding when to tear down secondary chats, and all
silently changed by it. isLastModalOpen also went from an indexed read to a full
iteration on a path called off the main thread. Both are reverted; the new behaviour
lives in isLastModalOpenNotClosing, which only this flow calls and which walks by
index.
Main thread. Making onCreated suspending moved the reassignment onto withBGApi's
single-thread dispatcher, so changeProfileTo mutated chatsContext, chatId and (via
changeActiveUser_) chatModel.users off Main, where the receiver loop writes the same
structures - the very hazard the same commit called out for its own users refresh.
onCreated and close() are now handed back to Dispatchers.Main, which is where both
pickers ran this before it became a callback.
Also: the in-flight flag moves to ChatModel so it is not a top-level val first created
inside a composition; the entry guard consults it, since with one manager per pane the
per-manager check alone let a second form open and silently drop its submit; the
one-time link picker's filteredProfiles is keyed on the profile count, so a profile
created from it appears without relying on the composition being rebuilt; and the
progress indicator skips its 500ms grace while creating, since that timer restarts
from zero in the rebuilt composition and would otherwise leave the rows looking idle.
§4 claimed keepingChatId was why the switch lands on the chat list; the cause was the
centre-pane modal placement nulling chatId. §4's rule about in-flight flags stopped at
lazy items and missed that the whole picker is disposed on Android. §7 justified the
sheet's placement with a two-sheet conflict that no longer applies at the deployment
target, and asserted the form stays open so the alert is shown - true, but not for the
reason given, and not at all where the alert was a SwiftUI .alert(item:).
Also records what is deliberately not fixed: a create interrupted by process death or
backgrounding leaves an orphan profile with nothing to reconcile it.
Mirrors the Android/desktop fixes where iOS has the same defects, and fixes the ones
that are specific to SwiftUI. Still not compiled - no Swift toolchain here, same
status as the rest of the iOS port in this branch.
Swallowed alert. The one-time link picker reported "the core ignored keepActiveUser"
by setting its own `alert` @State, consumed by .alert(item:) on the very view that is
presenting the create-profile sheet. UIKit refuses an alert on a controller that
already has a presentation, so it was never shown: the active profile silently
switched, the connection stayed where it was, and the user was left looking at an
unchanged form. Uses the global showAlert, which targets the top view controller, as
the compose picker already does.
Stale picker list. The compose picker fills its own `users` in onAppear and never
again, so the profile just created was missing from the list whenever the
reassignment failed - tapping "Add profile" again then hit "Duplicate display name!"
- and the frame height, which is computed from that count, was one row short. The
local from listUsers also shadowed the property, which is what hid this.
Resync failure reported as a creation failure. changeActiveUserAsync_ throws, and on
the "core ignored the flag" path that propagated into the form's catch, which says
"Error creating profile!" although the profile had been created. On the compose
picker that path also left chatId pointing at a prepared chat that had stayed with
the previous profile, i.e. at a chat absent from the list, which renders blank.
Sheet owner destroyed mid-dismissal. The compose picker's sheet was attached inside
profilePicker(), which is swapped out for currentSelection() the moment listExpanded
flips - which changeProfile does while the sheet is still dismissing, and which an
incoming event can do at any time via profileChangeProhibited. Moved to the Group in
viewBody(), which survives both; stacking sheets is supported from iOS 14.5 and the
app targets 15.
Dismissal during creation. The submit runs in an unstructured Task that SwiftUI does
not cancel, so a swipe-to-dismiss still created the profile and switched to it while
every state write, including the one clearing the in-flight flag, landed on a view
that was gone. Both sheets now block interactive dismissal while creating.
Latched picker. If apiChangeConnectionUser returns nothing rather than throwing,
profileSwitchStatus was never reset, leaving the picker dimmed behind a spinner with
hit testing off. Pre-existing, but the new row routes through the same handler.
Re-tappable row. switchingProfileByTimeout only latches half a second after the
switch starts, and creatingProfile is cleared as soon as the switch is handed over,
so the row was live in between.
Six defects, all in the window between creating the profile and moving the
invitation onto it.
Pane. The form was shown in ModalManager.center, which on desktop sets
ChatModel.chatId to null - and the chat view *is* the centre pane. Tapping "Add
profile" in a prepared invitation therefore closed that chat, discarding the typed
compose draft with it, and only the success path reopened it: cancelling the form,
or any failure, left "No selected chat" with the invitation nowhere on screen. From
the one-time link picker, which is itself a start-pane modal, the form appeared in a
different pane with the picker still live beside it and no scrim, so a profile could
be selected while the form was open - and apiChangeConnectionUser recreates the
connection, invalidating the pccConnId the form was about to use. The manager is now
passed in: end for the compose picker, matching the incognito modal it already opens
there, and start for the one-time link picker, matching its own.
In-flight guard. The flag was per-picker and released when the profile existed, not
when the invitation had moved. Both are wrong: onCreated only launches the
reassignment, and on Android the picker's composition is disposed while the form is
on top of it, so the flag it comes back with is a fresh false. There was no spinner,
no dimming and every row was live during the switch, so a second tap created a second
profile or started a competing reassignment. onCreated is now suspending so the flag
covers the whole flow, and the flag is a single top-level state both pickers read.
The compose picker had no in-progress state at all and now has one, and its new row
was also the only one missing the profileChangeProhibited guard.
Active user and host. The reassignment resolves the invitation under whatever profile
is active when it runs. A notification tap switches the active user from another
dispatcher, and a remote host connect/disconnect switches the host, so both are
checked before handing over rather than reassigning under the wrong profile.
Torn-down form. isLastModalOpen was used only to decide whether to close the form,
and the flow continued when it was false - reassigning and switching under a screen
the user had already left. It now stops.
Connection not moved. The one-time link picker dismissed itself and turned the
app-wide incognito default off whether or not apiChangeConnectionUser succeeded.
Unlike the prepared-chat reassignment, that call provisions a new queue, so it is
what fails offline - leaving a profile just created for the invitation stranded with
nothing pointing at it and the picker gone. It now keeps the picker open and only
clears incognito once the connection has actually moved.
Reopening the chat. chatModel.chatId was set unconditionally after the switch, before
the check that the switch actually happened, pointing the chat view at a chat that had
just moved to another profile and defeating the guard in updateChats that clears it.
Also applies the users refresh on the main thread, where the receiver loop updates
the same list, and skips it when the host has changed underneath.
closeModal leaves an animated modal in modalViews and only stages its index in
toRemove, drained when the animation ends. hasModalOpen and isLastModalOpen scanned
modalViews directly, so for the ~250ms of the close animation they still reported a
dismissed modal as open.
A caller that closes on "is my modal still the last one?" therefore popped the modal
underneath as well - on Android, where every placement shares one stack, backing out
of the create-profile form while creation was in flight dismissed the New chat screen
too. Both checks now skip the entries already staged for removal.
The generated docs still said "Create new user profile." with response "ActiveUser:
Active user profile", which is no longer true when keepActiveUser is set. The record
generator has no per-field description slot, so the command description is the only
place a bot author can learn this.
createUserRecordAt gates the "UPDATE users SET active_user = 0" on the activeUser
argument but called getNextActiveOrder unconditionally, so a profile created with
keepActiveUser got active_order = max + 1 despite never having been active. Every
picker sorts on that column descending, so the new profile sorted above the profile
actually in use - in the Android user picker, which sorts the full list, literally
above the active one.
active_order means "last activated" and setActiveUser is otherwise its only writer,
so a profile that was never activated now keeps 0 and sorts last until it is used.
Also asserts in the test that the created user's own record is not active: /u only
covers the currentUser TVar, and it is the user record that the clients branch on to
detect a core that ignored the flag. Moves the second test to "user profiles", which
is what it tests - it has nothing to do with short links.
Adds "Add profile" to the picker reached from "Share profile" on a one-time
link. The new profile is routed through the existing selectedProfile path, so
the connection change and the switch cannot drift from picking an existing one.
The row sits outside the active-profile branch: inside it, it would disappear
whenever the active profile is filtered out by the search text.
Adds "Add profile" to the profile picker shown above the compose box for a
prepared chat, so connecting as someone new does not mean leaving the invitation
to create a profile in settings.
NewUser and createActiveUser gain keepActiveUser, with apiCreateProfileKeepingActive
as the named entry point; the flag never appears at a call site. The profile is
created without becoming active so the profile that owns the prepared chat stays
active for the reassignment.
The row is emitted first: unlike the Android/desktop list this one is not
reverse-laid-out, so first renders at the top. After switching, the chat is
reopened explicitly - keepingChatId only preserves its place in the reloaded
list.
Reuses the existing "Add profile" and "Error changing chat profile" strings, so
no new translation entries.
CreateProfile hardcoded create-and-activate. Adds an optional onSubmit hook so
the same form can create a profile for an invitation, which must not switch the
active user until the prepared chat has been reassigned.
Optional rather than required, unlike the Kotlin equivalent: it touches no
existing call site, which matters more here because this cannot be compiled on
the machine the change was written on.
createProfileInNoProfileSetup passed the image positionally as Profile's 4th
argument, which is description, not image - so the chosen avatar was stored as
the profile description and no picture was set.
Both are String?, so it type-checked silently. The sibling createProfileInProfiles
avoids this by passing image by name.
Only reachable on desktop with no local chat profile: the user picker then offers
"Create chat profile" (desktopNoUserNoRemote), which opens the profile form with
its avatar picker, and localUserCreated is false so submission takes this branch.
Onboarding is unaffected - both first-profile screens take a name only, with no
avatar picker, and call createProfileOnboarding instead.
Unrelated to the rest of this branch - safe to drop or move to its own PR.
Adds "Add profile" to the profile picker reached from "Share profile" on a
one-time link, so a link can be handed out under a new identity without setting
one up in settings first. Reuses the existing users_add string.
Extracts the existing selection handler into selectProfile so the new profile
takes exactly the same path as picking an existing one - the connection change
and the switch cannot drift apart.
The row sits outside the active-profile branch: inside it, it would disappear
whenever the active profile is filtered out by the search text. It is only
offered when there is a connection to move, so it does not appear in the share
list, where a new profile would have nothing to share into.
ActiveProfilePicker performed the whole profile change - connection reassignment,
switch, alerts, close - inside the row's onSelected lambda, where nothing else can
reach it.
Lift it to selectProfile(user), called by the row. Pure move: the body is unchanged
apart from indentation, so picking a profile behaves exactly as before. It gives the
next commit a way to take the same path for a newly created profile rather than
duplicating it.
Opening someone's link prepares a chat and shows the profile picker above the
compose box. It listed existing profiles and Incognito, so connecting as
someone new meant leaving the invitation, creating a profile in settings, and
coming back to the link.
Adds "Add profile" to that picker, reusing the existing users_add string. The
profile is created without becoming active, so the profile that owns the
prepared chat stays active for the reassignment, which then switches once -
rather than switching away and back.
The row is emitted last, so with the reversed layout it renders at the top of
the expanded list with the current selection nearest the compose box.
After switching, the chat is reopened explicitly: keepingChatId only preserves
its place in the reloaded list, so without that the switch lands on the chat
list of the new profile rather than the invitation it was chosen for. This
applies to picking an existing profile too.
The form is only dismissed once creation succeeds, so a rejected duplicate name
does not discard what was typed. A dismissal guard keeps a back-tap during
creation from switching the profile anyway, and the created profile is put into
the picker list immediately so it stays visible if the reassignment fails.
If the core activated the profile despite keepActiveUser - an older remote host
ignoring the field - the reassignment would fail, so that case resyncs to what
the host actually did instead.
Updates the onboarding flow doc, which names the arguments of the command that
gains the field.
Two tests, both driving /_create user with JSON - the path the apps actually
use, which no test covered before:
- keepActiveUser creates the profile while leaving the active user alone, and
the prepared contact can then be reassigned to it and connected.
- omitting keepActiveUser still parses and still activates. This is a
regression guard: iOS does not send the field, so if the record ever stopped
accepting its absence, profile creation there would break silently.
Adds keepActiveUser to NewUser. When set, the new user is created but the
current active user is preserved.
This is needed to create a profile for an invitation: the prepared chat is
resolved under the active user by APIChangePreparedContactUser, so the profile
that owns the invitation has to stay active until the chat has been moved to
the new one. Creating and activating in one step makes that reassignment
impossible without switching back and forth.
BoolDef gives the field omittedField = False, so clients that do not send it -
iOS, the CLI, and any older caller - keep the current behaviour of activating
the new user.
keepActiveUser is ignored when there is no active user to keep, which would
otherwise leave the app with none at all.
Note the response is still CRActiveUser: it carries the created user, which is
not the active one on this path. Documented at the field.
Regenerates the API docs and the generated TypeScript and Python client types,
which are checked by the Bot API docs tests.
The generator emits BoolDef fields as required in the client types, so the two
hand-maintained clients that build a NewUser literal - simplex-chat-python and
simplex-chat-nodejs - stop type-checking until the field is added there too. No
test covers those; a4e3a1ea1 did the same when clientService was added.
changeActiveUser_ was called outside the guards that check whether
apiChangePreparedContactUser / apiChangePreparedGroupUser actually succeeded.
When the reassignment failed the app still switched to the other profile,
leaving the invitation behind in the original one - and keepingChatId then held
a chat belonging to a different user.
Reachable today: open a link, expand the profile picker, and have the connection
start in the window before tapping. profileChangeProhibited flips to true, the
API returns null, and the switch happened anyway.
The sibling picker in NewChatView already guards this correctly. iOS is
unaffected: there apiChangePreparedContactUser throws, so a failure skips the
switch by control flow.
CreateProfile hardcoded what happens after submission, choosing between
createProfileInProfiles and createProfileInNoProfileSetup inside the button
handler. That conditional is a hidden premise: two call sites invoke the
composable and neither can see from the call that submission behaves
differently for them.
Lift the choice to the callers. The callback passes raw fields rather than a
Profile because the two paths do not build the same one - the no-profile-setup
path drops shortDescr and passes a null remote host id - so a Profile-shaped
callback would silently change its behaviour.
The chatModel parameter was redundant besides: UserPicker passed the
platform-level chatModel global, which the parameter merely shadowed.
No behaviour change - the conditional moves verbatim into createProfileFromForm,
which both call sites invoke, rather than being copied into each.
Group chat and its support chats have the same chat id, so a draft saved
in "chat with admins" was restored in the group chat and shown as its draft
in the chat list. Include chat scope in the draft chat id.
Also save the draft when support chat is closed without changing chat id -
in that case KeyChangeEffect that saves drafts doesn't run.
* Translated using Weblate (German)
Currently translated at 100.0% (2899 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/
* Translated using Weblate (Russian)
Currently translated at 97.5% (2829 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.9% (2898 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/
* Translated using Weblate (Spanish)
Currently translated at 100.0% (2899 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/
* Translated using Weblate (Spanish)
Currently translated at 100.0% (2497 of 2497 strings)
Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/
* ru translations
* Translated using Weblate (German)
Currently translated at 100.0% (2899 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/
* Translated using Weblate (Russian)
Currently translated at 97.5% (2829 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.9% (2898 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/
* Translated using Weblate (Spanish)
Currently translated at 100.0% (2899 of 2899 strings)
Translation: SimpleX Chat/SimpleX Chat Android
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/
* Translated using Weblate (Spanish)
Currently translated at 100.0% (2497 of 2497 strings)
Translation: SimpleX Chat/SimpleX Chat iOS
Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/
* process translations
* ui: fix translation errors in es, ru, de, it, hu
Review of the translations in this PR against the English originals.
Values only - no keys added, removed or renamed.
Spanish:
- "The sender deleted the connection request." said "El servidor..."
(the server), attributing the deletion to a server rather than the
sender. Restored "El remitente".
- "Require/Do not require signing messages." were rendered as statements
("Se requieren mensajes firmados."), making the toggle options read
identically to the separate status strings "Message signing is
(not) required.". Restored the imperative.
- "...compare (or scan) the code on your devices." said "en tu
dispositivo" (singular); the code has to be compared on both.
- "How to register a test name" was pluralised to "nombres en pruebas".
Russian:
- "Помошь и поддержка" -> "Помощь и поддержка" (spelling).
- "File servers" was the calque "Файл серверы" -> "Серверы файлов",
matching the iOS translation of the same string.
- "Если Вы выберите" -> "выберете" (imperative used for future).
- "Create web preview." said "Создать веб страницу." (web page, also
missing the hyphen) -> "Создать веб-предпросмотр.".
- webpage_info dropped "to show your channel preview to visitors before
they subscribe"; webpage_url_footer said "страницы канала" instead of
the preview. Both restored from the iOS wording.
- "Enter description (optional)" lost "(optional)"; "Error adding
relays" was truncated to "Ошибка добавления".
- "Add contributors." used "авторов"; the member role is "соавтор".
- "удален(а)" -> "удалён", "подтвержденный" -> "подтверждённый".
German, Italian, Hungarian:
- The source strings "Names for your channel or business." and
"SimpleX names (BETA)" were renamed to "Public names..." and
"SimpleX public names (BETA)", but the old values were carried over,
so "public" was missing. Android German and Chinese had already been
updated in this PR; this brings iOS German, and both platforms for
Italian and Hungarian, in line.
- iOS German "How to register a test name" still read "Einen Test-Namen
registrieren", while Android German was updated in this PR.
---------
Co-authored-by: mlanp <github@lang.xyz>
Co-authored-by: Evgeny Poberezkin <e.poberezkin@me.com>
Co-authored-by: Hosted Weblate user 54392 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: No name <usir.alerts@onionmail.org>
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com>
* android, desktop: switch to the request's profile when accepting contact request from notification
* plans: justify accepting contact request from notification fix
* desktop: fix forwarding moving message draft to another chat
The same ComposeView is reused when switching chats, so the `chat`
captured by the desktop-only `onDispose` is the chat that was open when
the view was first composed, not the one the message is forwarded from.
The draft was saved under that stale chat id, and when it happened to be
the chat forwarded to, sending the forward restored the draft there.
* plan: justify desktop forward/draft fix and cross-platform findings