mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 08:33:09 +00:00
android, desktop: document online search in spec and product docs
apps/multiplatform/CODE.md requires spec and product updates with every code change. Mirror the iOS online search docs in the Kotlin docs' own formats and numbering, stating where Android and desktop differ, and fix the ConnectProgressManager signatures the branch's owner parameter had made stale. Run: 003-kotlin-directory-search, slice 6
This commit is contained in:
1 parent
d65c68c075
commit
33bae2658a
14 files changed
+426
-42
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Chat List Specification
|
||||
|
||||
Source: `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt`
|
||||
Source: `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt`, `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt`, `common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt`
|
||||
|
||||
---
|
||||
|
||||
@@ -16,6 +16,7 @@ Source: `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatLis
|
||||
8. [UserPicker](#8-userpicker)
|
||||
9. [Source Files](#9-source-files)
|
||||
10. [Crowdfunding Banner](#10-crowdfunding-banner)
|
||||
11. [Online Search](#11-online-search)
|
||||
|
||||
---
|
||||
|
||||
@@ -152,6 +153,7 @@ The `filteredChats` function (line ~1474) applies filters in this order:
|
||||
- Text input with search icon.
|
||||
- SimpleX link detection: When a pasted string contains a single SimpleX link, it triggers `planAndConnect` for connection, suppressing normal search.
|
||||
- Unread filter toggle button (right side, when search is empty).
|
||||
- Searching SimpleX Directory and resolving SimpleX names online happen only on request, below the local results: see [Online Search](#11-online-search).
|
||||
|
||||
---
|
||||
|
||||
@@ -314,6 +316,8 @@ Uses `AnimatedViewState` (`GONE`, `VISIBLE`, `HIDING`) with a `MutableStateFlow`
|
||||
| `TagListView.kt` | Tag management and assignment view |
|
||||
| `UserPicker.kt` | User switching side panel |
|
||||
| `GetStakeBanner.kt` | Crowdfunding banner and the shared banner card chrome |
|
||||
| `DirectorySearchView.kt` | Online search model, Names and Directory rows, first-search alert |
|
||||
| `model/DirectorySearch.kt` | Directory response parsing and online result types |
|
||||
|
||||
---
|
||||
|
||||
@@ -358,3 +362,121 @@ Tapping the card runs `openGetStake()`, which opens `GetStakeView(showFirstImage
|
||||
### Shared card chrome
|
||||
|
||||
`Modifier.bannerCard(onTap)` (size state, gradient brush, `heightIn`, `clip`, `background`, `clickable`) and `BannerDismissButton` are declared separately from `GetStakeBanner` so other banners can adopt the same chrome; the paddings stay with the caller. The gradient reuses `gradientPoints`, `lightStops` and `darkStops` from `views/newchat/OnboardingCards.kt`.
|
||||
|
||||
---
|
||||
|
||||
<a id="OnlineSearch"></a>
|
||||
|
||||
## 11. Online Search
|
||||
|
||||
**Files:** `views/chatlist/DirectorySearchView.kt` (model and rows), `model/DirectorySearch.kt` (response and result types), `views/chatlist/ChatListView.kt` (triggers and rendering), `views/helpers/SearchTextField.kt` (Search key)
|
||||
|
||||
The search text can be sent online on request: to SimpleX Directory, to find public groups and channels, and, when the text is a SimpleX name, to the user's name-resolving server. Results are shown below the local chats in two parts, Names then Directory. They are never saved and live only as long as the search text and the active profile: [`ChatListSearchBar`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L866-L970) calls `reset()` from a `KeyChangeEffect` on `searchText.value.text` and another on `chatModel.currentUser.value?.userId` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L911-L916)). The model is a plain `remember` in [`ChatListView`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L246), so an Android rotation (which recreates the Activity) drops the results while the `rememberSaveable` search text stays. The New chat sheet uses the same model and rows (see [Navigation, New Chat Online Search](navigation.md#10-new-chat-online-search)).
|
||||
|
||||
### Triggers
|
||||
|
||||
| Trigger | Handler | Runs |
|
||||
|---------|---------|------|
|
||||
| Search key: the keyboard's Search action on Android, Enter (key up) on desktop, through [`SearchTextField`](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt#L33-L43)`(onSearch)` | [`runOnlineSearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L995-L1000) | `search(text, name = connectNameCandidate.value)`: the directory's first page, and name resolution when the text is a name candidate |
|
||||
| "Search in Directory" row ([`SearchInDirectoryRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L202-L217)) | [`runDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L988-L993) | `searchDirectory(text)`: the directory only |
|
||||
|
||||
`SearchTextField` handles the key per platform: on Android through `KeyboardActions(onSearch)` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt#L116-L121)), on desktop through `onPreviewKeyEvent` for `Key.Enter` / `Key.NumPadEnter` on `KeyUp` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt#L95-L102)), so one key press runs the search once. Every caller keeps `ImeAction.Search`; a caller without `onSearch` gets no handler.
|
||||
|
||||
Both triggers do nothing for empty (trimmed) text or a pasted SimpleX link ([`searchTrimmed`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L985-L986), `searchShowingSimplexLink`), and both go through [`confirmDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L327-L333), which runs the search at once when `appPrefs.directorySearchAlertShown` is set and otherwise shows the three-button alert ([`showDirectorySearchAlert`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L335-L363), `showAlertDialogButtonsColumn` with `parseHtml = false` so the paragraph break shows): Search / Search and don't show again / Cancel. Its message names both recipients: the directory, and, for a SimpleX name, the server that resolves names. The preference is in `hintPreferences`, so "Reset all hints" brings the alert back. Each trigger launches the model method with `withApi`.
|
||||
|
||||
The rows next to the search field share one slot with the tags, laid out by [`TagsOrConnectByName`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1298-L1352) and [`ChatListSearchBarWithTags`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1219-L1244):
|
||||
|
||||
| Search text | Android, one-hand UI (slot above the field) | Android, top bar (slot below the field) | Desktop (either mode) |
|
||||
|-------------|-----------------------------------|------------------------------|------|
|
||||
| Empty | Tags | Tags | Tags |
|
||||
| Plain text | "Search in Directory" | Tags, then "Search in Directory" | Tags and "Search in Directory" |
|
||||
| Name candidate, no name results yet | "Connect to …" and "Search in Directory" | "Connect to …" (in the tags' place), then "Search in Directory" | Tags, "Connect to …" and "Search in Directory" |
|
||||
| Name candidate with name results | "Search in Directory" | Tags, then "Search in Directory" | Tags and "Search in Directory" |
|
||||
| Pasted SimpleX link | Tags | Tags | Tags |
|
||||
|
||||
On desktop the rows are passed to the caller's `desktopView`, which places them between the tags and the field: above the field with the bottom toolbar, below it with the top toolbar.
|
||||
|
||||
<a id="OnlineSearchModel"></a>
|
||||
|
||||
### OnlineSearchModel
|
||||
|
||||
**Location:** [`OnlineSearchModel`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L31-L199)
|
||||
|
||||
A plain class holding Compose state, owned by `ChatListView` as `remember { OnlineSearchModel() }` and passed through `ChatListWithLoadingScreen` to `ChatList`, `ChatListSearchBarWithTags`, `ChatListSearchBar` and `TagsOrConnectByName`. Every method runs on the main thread: the triggers and rows launch them with `withApi` (`Dispatchers.Main`), and the spinner's `onCancel` hops back with `withApi { cancelRequest() }`; the API calls move the FFI work to `Dispatchers.IO` themselves. This keeps every read and write of `generation` on one thread.
|
||||
|
||||
| State | Description |
|
||||
|-------|-------------|
|
||||
| `names: List<NameSearchResult>` | Name results for the current text |
|
||||
| `directory: List<DirectoryResult>` | Directory entries shown so far, each with the local chat its link refers to |
|
||||
| `loading` | A directory request is in flight |
|
||||
| `resolvingNames` | Name resolution is in flight |
|
||||
| `failed` | The last directory request failed |
|
||||
| `searched` | A directory request for the current text completed, successfully or not (tells "not searched" from "found nothing") |
|
||||
| `cursor` (private) | Opaque `JsonObject?` from the last page, echoed back for the next one; null when there are no more. Snapshot state, because `footer` reads it |
|
||||
| `generation` (private) | Bumped by `reset()` and `cancelRequest()`; a reply whose captured generation differs never writes model state |
|
||||
|
||||
The public state has private setters; `searchedText`, `generation` and `spinnerShown` are plain fields.
|
||||
|
||||
| Computed | Definition |
|
||||
|----------|------------|
|
||||
| [`searching`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L56) | `loading \|\| resolvingNames` |
|
||||
| [`showResults`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L57) | `searching \|\| searched \|\| names.isNotEmpty()` |
|
||||
| [`footer`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L58) | `DirectoryFooter.Retry` when `failed`, else `ShowMore` when a cursor exists, else null |
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| [`reset()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L60-L70) | Clears all results and flags, bumps `generation` (text change, profile switch) |
|
||||
| [`cancelRequest()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L73-L77) | The spinner's `onCancel`: bumps `generation` and clears `loading` and `resolvingNames`; results already shown stay |
|
||||
| [`search(text, name)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L79-L96) | Search key: ignored for empty text or while `searching` for the same text; otherwise `reset()`, then the first directory page and, with a name, name resolution concurrently (`coroutineScope { async { resolveNames(name, gen) } }`, with the generation captured before the child coroutine starts) |
|
||||
| [`searchDirectory(text)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L98-L111) | The row: ignored while `loading` for the same text; for the same text it clears only the directory state (name results stay), for new text it resets; then the first page |
|
||||
| [`loadMore()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L113-L116) | "Show more": the next page, appended |
|
||||
| [`retry()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L119-L125) | The retry row: resumes the page when a cursor exists, else repeats the first page; no reset |
|
||||
| [`rows(shownChatIds)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L127-L139) | The rows to render, given the ids of the local chats shown (see De-duplication) |
|
||||
|
||||
**Spinner.** The private setters of `loading` and `resolvingNames` call [`updateSpinner()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L185-L198) on every write, which starts the "Searching directory…" progress with owner `ConnectProgressOwner.DirectorySearch` when `searching` becomes true and stops it when it becomes false (`spinnerShown` keeps the last state, so repeated writes do nothing). The chat list shows it in the toolbar ([`ChatListToolbar`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L639-L645)) once it has run for a second, not in the search field. The spinner is shared with connections: [`ConnectProgressOwner`](../../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L50) records whose it is, [`startConnectProgress`](../../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L60-L69) does not let a search take it from a running connection, and [`stopConnectProgress`](../../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L72-L78) ignores a stop from another owner. Tapping a result calls [`planAndConnect`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt#L24-L55), whose `cancelConnectProgress()` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L81-L88)) cancels the search through its `onCancel` before the connection takes the spinner. Clearing the search text does the same through the typing path.
|
||||
|
||||
### Directory requests
|
||||
|
||||
[`request(append)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L159-L183) reads `chatModel.remoteHostId()` and calls [`apiSearchDirectory`](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1645-L1652), which sends `APISendServiceRequest` to [`DIRECTORY_SERVICE_LINK`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L16) with a [10-second timeout](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L19) and the request built by [`directorySearchRequest`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L70-L74) (`searchText`, and `searchCursor` for later pages). [`parseDirectorySearchResponse`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L78-L87) accepts only a `searchResults` response and decodes its entries one at a time inside `runCatching`, so an entry the app cannot decode is dropped and the rest of the page is kept.
|
||||
|
||||
[`DirectorySearchEntry`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L33-L57) keeps the wire field `groupLink` private; its [`link`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L47-L56) is a `@Transient` property whose initializer, run by the generated decoder, takes `groupLink.connShortLink ?: groupLink.connFullLink` and throws `SerializationException` unless [`simplexLinkType`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L59-L62) classifies it as `SimplexLinkType.group` or `SimplexLinkType.channel`. Being `@Transient`, `link` cannot be set by a `link` key in the JSON. `simplexLinkType` returns the link type only when [`strConnectTarget`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt#L834) finds exactly one SimpleX link whose text is the whole string. `link` is also the entry's `id`. The directory sends only the short link when an entry has one.
|
||||
|
||||
For each new entry of a page (not already shown, not repeated in the page), the request looks up the local chat its link refers to with `apiConnectPlan(rh, entry.link, resolveMode = PRMNever, inProgress = mutableStateOf(false), onFailure = ConnectPlanFailure.Log)` and [`planLocalChatId`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L89-L109), building a [`DirectoryResult`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L172-L177). The link-type check makes this lookup store-only: the core plans contact-mode links under `PRMNever` from the store, but would fetch an invitation link from its server. The page is published, appended or replacing, only if the generation did not change.
|
||||
|
||||
### Name resolution
|
||||
|
||||
[`resolveNames(name, gen)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L141-L157) resolves `[name]` for a candidate starting with `@` or `#`, else `["@$name", "#$name"]` (the candidate comes from [`nameSearchCandidate`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1261-L1272), as on the typing path). Each target is planned with `apiConnectPlan(rh, target, resolveMode = PRMUnknown, inProgress = mutableStateOf(false), onFailure = ConnectPlanFailure.Log)`; it stops before a target once the generation changed, so a cancelled or replaced search sends no further names. [`NameSearchResult(target, result)`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L111-L170) (a private constructor behind a companion `operator fun invoke` that returns null for no row) maps the plan with one exhaustive `when`: a contact address or group link gives a row (profile from the link data or the known chat), the user's own contact address, an invitation link and an error give none. `simplexName` is the plan's `planSimplexName?.shortStr`, `localChatId` comes from `planLocalChatId`, `iconName` is an `ImageResource`, and for a group link `resolvedLink` is the link the core resolved (`connLink.connShortLink`). Failures are logged at debug level and never shown as alerts; the false `inProgress` also keeps `sendCmdWithRetry` from showing its network retry alert.
|
||||
|
||||
### De-duplication
|
||||
|
||||
[`rows(shownChatIds)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L127-L139) returns an [`OnlineSearchRows`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L179-L185) at composition time, so pages added by "Show more" are filtered too:
|
||||
|
||||
- a name result is hidden when its `localChatId` is among the shown local chats;
|
||||
- a directory result is hidden when its `localChatId` is among the shown local chats or any name result's `localChatId`, or when its `entry.link` equals any name result's `resolvedLink` (all name results, shown or not);
|
||||
- `footer` passes through.
|
||||
|
||||
Only store-derived chat ids and the link the core resolved can hide a row, never a field the directory or a name's owner wrote. A mismatch in link encoding can only produce a duplicate row.
|
||||
|
||||
[GAP] A regular group the user joined through a different link, or through the directory's full link, is not found by the store lookup and still appears as a Directory row; tapping it goes through `planAndConnect`. See [GAP-08](../../product/gaps.md#gap-08-online-search-groups-joined-through-another-link-still-appear-as-directory-results).
|
||||
|
||||
### Rendering
|
||||
|
||||
| Where | Rows | Empty state |
|
||||
|-------|------|-------------|
|
||||
| [`ChatList`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1045-L1217) | `rows(shownChatIds)` with the ids of the shown chats ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1088)); after the local chats and before `ChatListFeatureCards`, [`nameRows`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L509-L523) then [`directoryRows`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L525-L544) | `NoChatsView` overlay only when no chat is shown, the model is not `searching` and `rows.isEmpty` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1195-L1200)); the `you_have_no_chats` overlay of `ChatListWithLoadingScreen` (no chats at all) is hidden while `showResults` |
|
||||
| Onboarding branch of [`ChatListWithLoadingScreen`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L459-L498) | A `Column` with `ChatListSearchBarWithTags` first (top bar) or last (one-hand UI), and between them a `Box(Modifier.weight(1f))` holding, while `showResults`, a `LazyColumnWithScrollBarNoAppBar` of the same parts with `shownChatIds = emptySet()` (reversed in one-hand mode, `imePadding()` in top-bar mode), otherwise `ConnectOnboardingView()` on Android | A centred "No chats found" item when not searching and `rows.isEmpty` |
|
||||
|
||||
The onboarding `Column` takes the toolbar and navigation-bar paddings and consumes the navigation-bar insets, so the results list's `imePadding()` does not count them twice. On desktop the onboarding pages are drawn in the centre pane, so the chat list pane shows only the search bar and the results. When a joined result adds a chat and the onboarding state ends, `ChatList` enters composition and its `LaunchedEffect(activeFilter.value)` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1214-L1216)) clears the search text, which resets the model; the iOS app keeps the search there.
|
||||
|
||||
Each part renders only when it has rows: the Names part when `rows.names` is not empty, the Directory part when `rows.directory` is not empty or `rows.footer` is set. `nameRows` and `directoryRows` are `LazyListScope` builders; each part starts with a header `item` ([`SearchPartHeader`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L546-L554): `body2`, secondary colour, `semantics { heading() }`) rather than a `stickyHeader`, and its rows are `items` keyed by `id`. In the reversed one-hand list a header therefore renders below its rows.
|
||||
|
||||
| Row | View | Tap |
|
||||
|-----|------|-----|
|
||||
| Name | [`NameSearchRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L266-L294): avatar, profile name in bold, the SimpleX name in parentheses (secondary, one `AnnotatedString`, one line), description (2 lines) | `planAndConnect(rhId, result.target, close = null, cleanup = null)` |
|
||||
| Directory entry | [`DirectorySearchRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L219-L264): avatar, `#` + name, the directory's SimpleX name claim as plain text (truncates first), description (2 lines), member or subscriber count, and the verb "Join channel" / "Join group" (one line, never wrapped) | `planAndConnect(rhId, result.entry.link, close = null, cleanup = null)` |
|
||||
| Retry | [`DirectoryRetryRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L313-L325): "Search failed, tap to retry" | `withApi { retry() }` |
|
||||
| Show more | [`DirectoryShowMoreRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L296-L311): "Show more", disabled (secondary colour) while `loading` | `withApi { loadMore() }` |
|
||||
|
||||
A name or directory row tap first hides the keyboard (`hideKeyboard(view)`), then runs `planAndConnect` with `withBGApi`. Text from the directory and from name owners is rendered with `Text(String)` (never parsed as markdown) with line limits.
|
||||
|
||||
[GAP] Resolving a SimpleX name with `PRMUnknown` also loads the resolved address's link data from the server that hosts it; the alert names only the directory and the name-resolving server. See [GAP-09](../../product/gaps.md#gap-09-online-search-first-search-alert-does-not-mention-the-server-hosting-a-resolved-address).
|
||||
@@ -15,6 +15,7 @@ Source: `common/src/commonMain/kotlin/chat/simplex/common/App.kt` (470 lines)
|
||||
7. [Authentication Gate](#7-authentication-gate)
|
||||
8. [Onboarding Flow](#8-onboarding-flow)
|
||||
9. [Source Files](#9-source-files)
|
||||
10. [New Chat Online Search](#10-new-chat-online-search)
|
||||
|
||||
---
|
||||
|
||||
@@ -376,3 +377,31 @@ The stage value is stored in `appPrefs.onboardingStage` and persisted across app
|
||||
| `views/SplashView.kt` | Splash / loading screen |
|
||||
| `views/call/CallView.kt` | In-call fullscreen view (ActiveCallView) |
|
||||
| `views/localauth/PasswordEntry.kt` | Column divider utility (contains VerticalDivider) |
|
||||
| `views/newchat/NewChatSheet.kt` | New chat sheet: action buttons, contacts list, online search sections; Archived contacts (`DeletedContactsView`) |
|
||||
|
||||
---
|
||||
|
||||
<a id="NewChatOnlineSearch"></a>
|
||||
|
||||
## 10. New Chat Online Search
|
||||
|
||||
**Location:** [`NewChatSheetLayout`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L110-L535)
|
||||
|
||||
`NewChatSheetLayout` offers the chat list's online search (see [Chat List, Online Search](chat-list.md#11-online-search)) with its own [`OnlineSearchModel`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L141) (`remember`), reset from a `KeyChangeEffect` on `searchText.value.text` and another on `chatModel.currentUser.value?.userId` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L182-L187)). A profile switch also closes all modals on Kotlin, so the sheet is disposed anyway. The sheet's `DisposableEffect` `onDispose` ([`NewChatSheet`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L42-L48)) calls `cancelConnectProgress()`, which cancels a search still in flight. The model is also dropped while a modal opened from the sheet covers it (`ModalManager` composes only the top modal) and on an Android rotation.
|
||||
|
||||
| Element | Where | Behavior |
|
||||
|---------|-------|----------|
|
||||
| Search key | [`ContactsSearchBar`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L555-L657) with `onSearch` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L563)), passed to `SearchTextField` | the keyboard's Search action (Android) or Enter (desktop) runs [`runOnlineSearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L237-L242): `search(text, name = connectNameCandidate.value)` |
|
||||
| "Connect to …" row | the sticky header, below the field, in both `OneHandLazyColumn` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L387-L391)) and `NonOneHandLazyColumn` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L485-L489)) | shown for a name candidate until name results arrive (`candidate != null && onlineSearch.names.isEmpty()`) |
|
||||
| "Search in Directory" row | after the "Connect to …" row, behind a `Divider` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L392-L395), [source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L490-L493)) | shown for non-empty, non-link text; runs [`runDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L231-L235): `searchDirectory(text)` |
|
||||
| Names and Directory sections | after `itemsIndexed(filteredContactChats)` in both columns, [`onlineSearchSections(rows)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L195-L227) | each part starts with an [`OnlineSearchHeader`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L189-L193) item styled as "Your contacts" (`SectionDividerSpaced` then the `SectionView` title, not upper-cased); rows are `items` keyed by `id`; the Directory part ends with the retry or "Show more" row; rows tap `hideKeyboard(view)` then `withBGApi { planAndConnect(rhId, …, close = close, cleanup = null) }`, which closes the sheet once connected |
|
||||
|
||||
Both triggers do nothing for empty text or a pasted link ([`searchTrimmed()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L229), `searchShowingSimplexLink`) and go through `confirmDirectorySearch`, launching the model with `withApi`. `onlineSearchSections`, `OnlineSearchHeader`, `searchTrimmed`, `runDirectorySearch` and `runOnlineSearch` are local functions of `NewChatSheetLayout`. In the reversed one-hand column a header item renders below its rows, as the contacts header does.
|
||||
|
||||
**De-duplication against the contacts list.** `filteredContactChats` (the contacts the list shows, computed in the layout's scope) gives the shown ids: `rows = onlineSearch.rows(shownChatIds = filteredContactChats.map { it.id }.toSet())` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L180)), so no extra helper is needed.
|
||||
|
||||
**Empty state.** `showsEmptyState = !onlineSearch.searching && rows.isEmpty` ([source](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L181)); [`NoFilteredContactsItem`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L324-L338) shows "No filtered contacts" only when it holds. Kotlin has no "No contacts" state.
|
||||
|
||||
**Archived contacts.** [`DeletedContactsView`](../../common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt#L750-L864) passes no `onSearch` to `ContactsSearchBar`: its field keeps the Search IME label (every `SearchTextField` uses `ImeAction.Search`), the key does nothing, and it shows no online rows; its empty state is unchanged.
|
||||
|
||||
[GAP] New chat does not resolve a typed name locally while typing (the `ContactsSearchBar` collector resolves a name only when its "Connect to …" row is tapped), so a known contact whose name does not contain the typed text is not in "Your contacts" and appears as a Names row; tapping it opens the existing chat. See [GAP-10](../../product/gaps.md#gap-10-online-search-new-chat-does-not-narrow-contacts-by-name-while-typing).
|
||||
Reference in new issue
Block a user