Commit Graph
12 Commits
Author SHA1 Message Date
Narasimha-sc 02eaa882ac fix the docs the API merge invalidated, and use the async listUsers
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.
2026-08-06 17:49:37 +00:00
Narasimha-sc 2a7eb7eed2 fix the review's findings and cut the remaining API surface
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.
2026-08-06 17:27:35 +00:00
Narasimha-sc 937e688c0a android, desktop, ios: make the invitation profile flow surgical
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.
2026-08-06 17:03:40 +00:00
Narasimha-sc b9eb86541c ios: don't capture a mutable local in a @Sendable closure
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.
2026-08-06 16:12:26 +00:00
Narasimha-sc 147955f52d ios: fix the fallback ordering and close the input window on both pickers
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.
2026-08-06 15:32:17 +00:00
Narasimha-sc 49a8cafa94 ios: fix the fallback paths and guard the picker during the reassignment
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.
2026-08-06 14:17:37 +00:00
Narasimha-sc 4a169a3061 ios: fix the create-profile-for-invitation flow
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.
2026-08-06 13:45:11 +00:00
Narasimha-sc 9a69662970 ios: offer creating a profile when accepting an invitation
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.
2026-08-03 16:12:10 +00:00
EvgenyGitHubspaced4ndyEvgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
80538850f1 ui: show badges in more contexts (#7084)
* core: fix delivery cursor not advancing to maximum group member id for posgtgres (#7043)

* ui: show badge in user picker above message entry

* core: send badge with channel owner profile

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-06-17 19:10:37 +01:00
2dd54c6697 core, ui: prohibit changing profile for prepared entity when first attempt to connect failed (#6037)
* core: prohibit changing profile for prepared entity when first attempt to connect failed

* reuse incognito

* schema

* ios

* postgres schema

* ios

* reenable tests

* kotlin

* update alert

* rename predicate, combine queries

* send the correct incognito mode, fail on attempt to change mode for prepared connection

* query plans

* ui: show group connecting status

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2025-07-05 11:09:10 +01:00
EvgenyandGitHub 9794829d74 core: show group as sender, add contact card to contact request chats, unify api type for prepared and contact request chats (#6003)
* core: option to show group as sender of chat item (for the initial items, e.g. welcome message)

* add chat item to contact request chats

* return AChat for prepared chats and contact requests

* update iOS api types, show prepared contact as blue, show preview info when content message is not available (previously was showing feature item)

* ios: remove ContactType

* ios: show group as sender, fix avatar not showing when member message sequence starts with merged items

* ios: update compose UI for all connection scenarios

* address settings

* ios: address settings UI

* fix tests

* fix tests 2

* ios: fix minor issues
2025-06-23 14:42:00 +01:00
spaced4ndyandGitHub b0ee13628b core: change user for prepared contact or group (#5985) 2025-06-13 14:38:17 +00:00