mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-16 02:30:19 +00:00
report the covered-form case, and stop gating local expand/collapse
The form being covered by another modal was the only branch here that said nothing: the profile exists, the invitation did not move, and the form comes back with the name still typed so Create fails as a duplicate. Add isModalOpenNotClosing to tell covered from dismissed - excluding toRemove, or the close animation would report a dismissal as a cover. Gate the compose picker's branches rather than its rows. Master had no gate there at all, so blocking the local expand/collapse was a regression this branch introduced; only changeProfile and the incognito writes need it.
This commit is contained in:
+12
-3
@@ -426,10 +426,19 @@ fun createProfileForInvitation(rhId: Long?, onCreated: suspend (User) -> Unit) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_user))
|
||||
return@withApi
|
||||
}
|
||||
// Form gone - the user backed out of it. Don't move the invitation under a screen
|
||||
// they left, and don't report an error for something they did on purpose.
|
||||
// Form no longer on top. Don't move the invitation under a screen the user left -
|
||||
// but the two ways to get here need different treatment.
|
||||
if (!modalManager.isLastModalOpenNotClosing(ModalViewId.CONTEXT_USER_PICKER_NEW_PROFILE)) {
|
||||
Log.i(TAG, "createProfileForInvitation: form closed before the invitation moved")
|
||||
if (modalManager.isModalOpenNotClosing(ModalViewId.CONTEXT_USER_PICKER_NEW_PROFILE)) {
|
||||
// Still there, just covered - a deep link or notification action opened a modal
|
||||
// over it, which on Android shares the one stack. The form comes back with the
|
||||
// name still typed, and Create would then fail as a duplicate, so say what
|
||||
// happened rather than leaving the only silent branch in this function.
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_user))
|
||||
} else {
|
||||
// Genuinely dismissed - don't report an error for something they chose.
|
||||
Log.i(TAG, "createProfileForInvitation: form closed before the invitation moved")
|
||||
}
|
||||
return@withApi
|
||||
}
|
||||
close()
|
||||
|
||||
+8
-5
@@ -143,16 +143,19 @@ fun ComposeContextProfilePickerView(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
|
||||
.clickable(enabled = !busy, onClick = {
|
||||
// busy gates the branches that change something, not the row: this picker has no
|
||||
// spinner or dimming, so gating the row left it inert with no feedback and not even
|
||||
// collapsible during a slow change. Expanding and collapsing is local. As on iOS.
|
||||
.clickable(onClick = {
|
||||
if (!chat.chatInfo.profileChangeProhibited) {
|
||||
if (selectedUser.value.userId == user.userId) {
|
||||
if (!incognitoDefault) {
|
||||
listExpanded.value = !listExpanded.value
|
||||
} else {
|
||||
} else if (!busy) {
|
||||
chatModel.controller.appPrefs.incognito.set(false)
|
||||
listExpanded.value = false
|
||||
}
|
||||
} else {
|
||||
} else if (!busy) {
|
||||
changeProfile(user)
|
||||
}
|
||||
} else {
|
||||
@@ -186,11 +189,11 @@ fun ComposeContextProfilePickerView(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
|
||||
.clickable(enabled = !busy, onClick = {
|
||||
.clickable(onClick = {
|
||||
if (!chat.chatInfo.profileChangeProhibited) {
|
||||
if (incognitoDefault) {
|
||||
listExpanded.value = !listExpanded.value
|
||||
} else {
|
||||
} else if (!busy) {
|
||||
chatModel.controller.appPrefs.incognito.set(true)
|
||||
listExpanded.value = false
|
||||
}
|
||||
|
||||
+6
@@ -117,6 +117,12 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
|
||||
fun isLastModalOpen(id: ModalViewId): Boolean = modalViews.lastOrNull()?.id == id
|
||||
|
||||
/** [hasModalOpen], but a modal already dismissed and only waiting out its close animation
|
||||
* does not count. Together with [isLastModalOpenNotClosing] this separates "covered by
|
||||
* another modal" from "dismissed", which the last-position test alone cannot. */
|
||||
fun isModalOpenNotClosing(id: ModalViewId): Boolean =
|
||||
modalViews.withIndex().any { (i, m) -> m.id == id && i !in toRemove }
|
||||
|
||||
/** [isLastModalOpen], but a modal already dismissed and only waiting out its close
|
||||
* animation does not count - [closeModal] leaves it in [modalViews] until then. Separate
|
||||
* from [isLastModalOpen], which gates secondary chat teardown on the existing behaviour. */
|
||||
|
||||
@@ -379,7 +379,15 @@ re-derive them.
|
||||
The migration screens have the same fall-through, but they are not layered over a screen
|
||||
with its own handler. The separate case below is still open.
|
||||
|
||||
- **A modal pushed on top of the create form is indistinguishable from backing out of it**
|
||||
- ~~**A modal pushed on top of the create form is indistinguishable from backing out of it**~~
|
||||
**Fixed.** `isModalOpenNotClosing` was added beside `isLastModalOpenNotClosing`, so the
|
||||
two cases now separate: still present but not on top means covered — report it, because
|
||||
the form returns with the name still typed and Create would fail as a duplicate; absent
|
||||
means dismissed — stay quiet, the user chose that. Neither case moves the invitation, and
|
||||
neither calls `close()`, which would still pop the covering modal. Original note kept
|
||||
below for the reasoning about why proceeding is not an option:
|
||||
|
||||
- **(superseded) A modal pushed on top of the create form is indistinguishable from backing out of it**
|
||||
(`WelcomeView.kt`). The guard is `!isLastModalOpenNotClosing(...)`, which is also false
|
||||
when another modal covers the form — on Android all four `ModalManager` placements share
|
||||
one stack, so a deep link or notification action arriving mid-creation makes it fire:
|
||||
@@ -420,9 +428,11 @@ Three more, from a later round:
|
||||
The justification originally given — that holding it still avoids swapping
|
||||
`profilePicker()` for `currentSelection()` mid-flight — was borrowed from iOS, where that
|
||||
swap disposes a presented sheet; Kotlin has no sheet to lose, so it does not apply here.
|
||||
Left as is only because the change is cosmetic and touching these rows again is the kind
|
||||
of edit that has repeatedly introduced defects in this branch. Gate the work-starting
|
||||
branches instead, as iOS does, if this picker is revisited.
|
||||
**Fixed** by gating the branches rather than the row, as iOS does. Only `changeProfile`
|
||||
starts async work; the two `incognito.set` branches change state and are gated too;
|
||||
expanding and collapsing is local and is now always available. This was a regression
|
||||
introduced by this branch — master had no gate on these rows at all — which is what
|
||||
finally settled it, after being raised three times and deferred twice.
|
||||
|
||||
- **`submitting:` and `.interactiveDismissDisabled(...)` are read inside the `.sheet`
|
||||
content closure.** If SwiftUI does not re-invoke that closure when the presenting view's
|
||||
@@ -528,3 +538,12 @@ user just dismissed — the mistake fixed once already in this branch, where
|
||||
What *is* true: pressing "Add profile" a second time with the same name fails as a
|
||||
duplicate. That is the core refusing to create a profile that already exists, and the fix
|
||||
is to tap the row rather than recreate it.
|
||||
|
||||
One consequence of the registration placement (§8), recorded so it is a choice and not an
|
||||
oversight: on the stale-core path — an old remote host activated the profile despite
|
||||
`keepActiveUser` — the profile is **not** added to `chatModel.users` if the resync then
|
||||
throws. It is registered below that branch precisely because registering above it left two
|
||||
entries flagged `activeUser`, which makes `firstOrNull { it.activeUser }` resolve to
|
||||
whichever comes first. Corrupting which profile reads as active is worse than a missing
|
||||
row, and the missing row needs an outdated remote host *and* a failed resync to appear at
|
||||
all; the alert on that path tells the user something went wrong either way.
|
||||
|
||||
Reference in New Issue
Block a user