desktop: hide SimpleX address and chat preferences rows with no profile and no mobile in use (#7717)

This commit is contained in:
Narasimha-sc
2026-10-15 05:37:37 +01:00
committed by GitHub
parent 000258224c
commit 23ca3ea42f
4 changed files with 81 additions and 29 deletions
@@ -217,41 +217,43 @@ fun UserPicker(
@Composable
fun SecondSection() {
UserPickerOptionRow(
painterResource(MR.images.ic_qr_code),
if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address),
showCustomModal { it, close ->
val user = it.currentUser.value
if (user != null) {
UserAddressView(it, shareViaProfile = user.addressShared, close = close)
} else {
val modal = this
LaunchedEffect(modal, ModalManager.start.modalCount.value) {
if (ModalManager.start.isLastModal(modal)) close()
}
}
}, disabled = stopped
)
UserPickerOptionRow(
painterResource(MR.images.ic_toggle_on),
stringResource(MR.strings.chat_preferences),
click = if (stopped) null else ({
showCustomModal { m, close ->
val user = m.currentUser.value
if (!chatModel.desktopNoUserNoRemote) {
UserPickerOptionRow(
painterResource(MR.images.ic_qr_code),
if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address),
showCustomModal { it, close ->
val user = it.currentUser.value
if (user != null) {
PreferencesView(m, user, close)
UserAddressView(it, shareViaProfile = user.addressShared, close = close)
} else {
val modal = this
LaunchedEffect(modal, ModalManager.start.modalCount.value) {
if (ModalManager.start.isLastModal(modal)) close()
}
}
}()
}),
disabled = stopped
)
}, disabled = stopped
)
UserPickerOptionRow(
painterResource(MR.images.ic_toggle_on),
stringResource(MR.strings.chat_preferences),
click = if (stopped) null else ({
showCustomModal { m, close ->
val user = m.currentUser.value
if (user != null) {
PreferencesView(m, user, close)
} else {
val modal = this
LaunchedEffect(modal, ModalManager.start.modalCount.value) {
if (ModalManager.start.isLastModal(modal)) close()
}
}
}()
}),
disabled = stopped
)
}
if (appPlatform.isDesktop) {
Divider(Modifier.padding(DEFAULT_PADDING))
if (!chatModel.desktopNoUserNoRemote) Divider(Modifier.padding(DEFAULT_PADDING))
val inactiveUsers = users.filter { !it.user.activeUser }
@@ -28,8 +28,8 @@ Overlay panel triggered from `ChatListView` toolbar:
| Device picker row | `DevicePickerRow` showing local device and connected remote hosts (Desktop only); pill-shaped buttons with connect/disconnect actions |
| Active user profile | `ProfilePreview` of current user (Desktop: single row; Android: full user list) |
| User list | `UserPickerUsersSection` with all visible non-hidden profiles; tap to switch, long-press disabled |
| SimpleX address | Row to open `UserAddressView` (create or view address); with no active profile the screen closes itself (if another screen covers it, when the user goes back to it) |
| Chat preferences | Row to open `PreferencesView`; with no active profile the screen closes itself (if another screen covers it, when the user goes back to it) |
| SimpleX address | Row to open `UserAddressView` (create or view address); hidden on Desktop when there is no active profile and no mobile is in use; a screen shown with no active profile closes itself (if another screen covers it, when the user goes back to it) |
| Chat preferences | Row to open `PreferencesView`; hidden on Desktop when there is no active profile and no mobile is in use; a screen shown with no active profile closes itself (if another screen covers it, when the user goes back to it) |
| Chat profiles | Row to open `UserProfilesView` (or `CreateProfile` when no users exist on Desktop) |
| Use from desktop/mobile | Android: "Use from desktop" (`ConnectDesktopView`); Desktop: "Link a mobile" / "Linked mobiles" (`ConnectMobileView`) |
| Settings | Row to open `SettingsView` with `ColorModeSwitcher` trailing |
@@ -279,6 +279,7 @@ fun UserPicker(
- **Android:** Renders as a slide-up overlay panel on the chat list, triggered by tapping the user profile button in the toolbar.
- **Desktop:** Rendered inline in the left column of `DesktopScreen`, always accessible.
- Closes automatically when any `ModalManager.start` modal opens.
- On Desktop, when there is no active user and no mobile is in use (`desktopNoUserNoRemote`), the SimpleX address and Chat preferences rows and the divider below them are hidden.
- The SimpleX address row opens `UserAddressView` in `ModalManager.start`. With no active user (`currentUser == null`) the modal draws nothing and closes itself when it is the top modal (`ModalManager.start.isLastModal`); if another screen covers it, it closes when the user goes back to it.
- The Chat preferences row closes its modal the same way when there is no active user.
@@ -0,0 +1,49 @@
# Hide the address and preferences rows when there is no profile and no mobile in use
## Problem
On a desktop with no active profile and no mobile in use, the user picker shows "Create SimpleX address" as its first option row, then "Chat preferences" and, below a divider, "Create chat profile". The picker opens by itself in this state when no left-panel screen is open (`App.kt`). Neither of the first two rows can work without a profile:
- "Create SimpleX address" crashed with a NullPointerException at `UserPicker.kt:223` (`:216` in v6.1, `:222` in v6.2–v6.4) before #7679. GitHub reports from using the row: #5068, #5476, #6194, #7067 (Linux; comments on #5068 and #6194 report the same on Windows 11).
- "Chat preferences" left an invisible modal with no back button.
- With #7679 both screens close themselves instead, so a click only hides the picker and nothing opens.
Users have told the team that the crash stops them from creating a profile. On v7.0.4 a profile can still be created from "Create chat profile" after the crash, but the first row the user tries is one that cannot work.
The same crash also happens when a mobile disconnects while its address screen is open (#6452, macOS: the same stack, reported alongside phone disconnects). That path is fixed by #7679, not by this change.
## Solution summary
Hide the "SimpleX address" and "Chat preferences" rows when the desktop has no active user and no mobile in use (`chatModel.desktopNoUserNoRemote`). The divider below them is hidden with them. This is the state in which the picker already shows "Create chat profile" instead of "Your chat profiles". While a connected mobile is in use, both rows stay and act on the mobile's profile; if the mobile has no active profile, the screens close themselves (#7679).
Before the new user picker (#4796), `SettingsView` showed both rows only inside `if (profile != null)`, with "Create chat profile" in the `else if (chatModel.localUserCreated.value == false)` branch.
## Technical design
`SecondSection` in `UserPicker.kt` emits both rows inside `if (!chatModel.desktopNoUserNoRemote)`. The desktop divider below them belongs to the same group and is drawn under the same condition. The composable getter reads `currentUser` and `currentRemoteHost` state, so the picker updates as soon as a profile is created, a mobile is connected, selected or disconnected, or the desktop is switched to "This device" with no profile. On Android `desktopNoUserNoRemote` is always false, so nothing changes there.
The self-closing modals from #7679 stay. Hiding the rows only removes the entry point in that state. A screen shown with no active user in any other state still relies on #7679's handling; #7679's plan lists the paths. For example:
- a screen opened while there was a user, which then loses it: a mobile disconnects, or the self-destruct passcode is entered, with the address or preferences screen open;
- a mobile in use with no active profile (its last visible profile deleted): the rows are shown, and the screens close themselves.
### Security
UI-only: it hides two rows and the divider below them, based on local state. No data or trust boundary is involved.
## Implementation steps
1. `views/chatlist/UserPicker.kt`: in `SecondSection`, wrap the "SimpleX address" and "Chat preferences" rows in `if (!chatModel.desktopNoUserNoRemote)`, and draw the desktop divider below them under the same condition.
2. Documentation, under `apps/multiplatform/`:
- `spec/client/chat-list.md`, UserPicker section: when the two rows are hidden.
- `product/views/user-profiles.md`: when both rows are hidden.
## Verification
Desktop AppImage of this branch, fresh database, the v7.1.0-beta.6 CLI as the linked mobile; Android debug APK of this branch on Waydroid.
- Mobile connected, no desktop profile: both rows are shown for the mobile's profile. "Create SimpleX address" creates the address on the mobile (`/show_address` on the CLI returns it).
- Mobile connected, switched to "This device" (no profile): both rows are hidden and "Create chat profile" is shown. Switching back to the mobile shows them again.
- Address screen open, then the mobile disconnects: the screen closes (#7679), no crash, and the picker then shows no address or preferences rows.
- "Create chat profile" from that state: the profile is created, both rows appear and open their screens.
- No profile, mobile linked but not in use: the device row, then "Create chat profile", with no divider. After deleting the linked mobile ("Linked mobiles", right-click, "Delete"): "Create chat profile" is the first row. The spacing matches the gap between rows. After creating a profile, both rows and the divider are back.
- Android, with a profile: both rows are shown and open their screens; no crash in logcat.