mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 21:52:28 +00:00
android, desktop: hide a search result once the chat opened from it is listed
The same change as on iOS: the rows without a local chat look it up again when a connection started from one ends, so a group or channel opened from its row is no longer listed twice until the next search.
This commit is contained in:
1 parent
1c24109730
commit
4e01032a2e
4 files changed
+44
-19
No files matched your search
+3
@@ -122,6 +122,9 @@ class NameSearchResult private constructor(
|
||||
) {
|
||||
val id: String get() = target
|
||||
|
||||
fun withLocalChatId(localChatId: ChatId?): NameSearchResult =
|
||||
NameSearchResult(target, simplexName, profileName, descr, image, iconName, localChatId, resolvedLink)
|
||||
|
||||
companion object {
|
||||
operator fun invoke(target: String, result: ConnectionPlanResult): NameSearchResult? {
|
||||
val profile: NamedChat?
|
||||
|
||||
+5
-5
@@ -483,7 +483,7 @@ private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFiel
|
||||
state = resultsState,
|
||||
reverseLayout = oneHandUI.value
|
||||
) {
|
||||
nameRows(rows.names, oneHandUI.value)
|
||||
nameRows(rows.names, onlineSearch, oneHandUI.value)
|
||||
directoryRows(rows, onlineSearch, oneHandUI.value)
|
||||
if (!onlineSearch.searching && rows.isEmpty) {
|
||||
item {
|
||||
@@ -514,7 +514,7 @@ private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFiel
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.nameRows(names: List<NameSearchResult>, oneHandUI: Boolean) {
|
||||
private fun LazyListScope.nameRows(names: List<NameSearchResult>, onlineSearch: OnlineSearchModel, oneHandUI: Boolean) {
|
||||
if (names.isNotEmpty()) {
|
||||
searchPart(MR.strings.online_search_names, oneHandUI) {
|
||||
items(names, key = { it.id }) { result ->
|
||||
@@ -523,7 +523,7 @@ private fun LazyListScope.nameRows(names: List<NameSearchResult>, oneHandUI: Boo
|
||||
result,
|
||||
Modifier.clickable {
|
||||
hideKeyboard(view)
|
||||
withBGApi { planAndConnect(chatModel.remoteHostId(), result.target, close = null, cleanup = null) }
|
||||
withBGApi { planAndConnect(chatModel.remoteHostId(), result.target, close = null, cleanup = { withApi { onlineSearch.refreshLocalChatIds() } }) }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -540,7 +540,7 @@ private fun LazyListScope.directoryRows(rows: OnlineSearchRows, onlineSearch: On
|
||||
result.entry,
|
||||
Modifier.clickable {
|
||||
hideKeyboard(view)
|
||||
withBGApi { planAndConnect(chatModel.remoteHostId(), result.entry.link, close = null, cleanup = null) }
|
||||
withBGApi { planAndConnect(chatModel.remoteHostId(), result.entry.link, close = null, cleanup = { withApi { onlineSearch.refreshLocalChatIds() } }) }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1203,7 +1203,7 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
|
||||
} }
|
||||
ChatListNavLinkView(chat, nextChatSelected)
|
||||
}
|
||||
nameRows(rows.names, oneHandUI.value)
|
||||
nameRows(rows.names, onlineSearch, oneHandUI.value)
|
||||
directoryRows(rows, onlineSearch, oneHandUI.value)
|
||||
if (showsCards && !addressCreationCardShown.value) {
|
||||
item {
|
||||
|
||||
+23
-2
@@ -139,6 +139,28 @@ class OnlineSearchModel {
|
||||
)
|
||||
}
|
||||
|
||||
// a chat opened from a result exists only once its connection flow ends, so rows without a chat look again then
|
||||
suspend fun refreshLocalChatIds() {
|
||||
val rh = chatModel.remoteHostId()
|
||||
val gen = generation
|
||||
val nameChatIds = mutableMapOf<String, ChatId>()
|
||||
for (result in names) {
|
||||
if (result.localChatId == null) localChatId(rh, result.target)?.let { nameChatIds[result.id] = it }
|
||||
}
|
||||
val directoryChatIds = mutableMapOf<String, ChatId>()
|
||||
for (result in directory) {
|
||||
if (result.localChatId == null) localChatId(rh, result.entry.link)?.let { directoryChatIds[result.id] = it }
|
||||
}
|
||||
if (gen != generation) return
|
||||
names = names.map { r -> r.withLocalChatId(r.localChatId ?: nameChatIds[r.id]) }
|
||||
directory = directory.map { r -> r.copy(localChatId = r.localChatId ?: directoryChatIds[r.id]) }
|
||||
}
|
||||
|
||||
private suspend fun localChatId(rh: Long?, link: String): ChatId? {
|
||||
val plan = chatModel.controller.apiConnectPlan(rh, link, resolveMode = PlanResolveMode.PRMNever, inProgress = mutableStateOf(false), onFailure = ConnectPlanFailure.Log)
|
||||
return plan?.let { planLocalChatId(it.connectionPlan) }
|
||||
}
|
||||
|
||||
private suspend fun resolveNames(name: String, gen: Int) {
|
||||
val rh = chatModel.remoteHostId()
|
||||
val targets = if (name.startsWith("@") || name.startsWith("#")) listOf(name) else listOf("@$name", "#$name")
|
||||
@@ -168,8 +190,7 @@ class OnlineSearchModel {
|
||||
val seen = directory.map { it.id }.toMutableSet()
|
||||
for (entry in r.entries) {
|
||||
if (!seen.add(entry.id)) continue
|
||||
val plan = chatModel.controller.apiConnectPlan(rh, entry.link, resolveMode = PlanResolveMode.PRMNever, inProgress = mutableStateOf(false), onFailure = ConnectPlanFailure.Log)
|
||||
page.add(DirectoryResult(entry = entry, localChatId = plan?.let { planLocalChatId(it.connectionPlan) }))
|
||||
page.add(DirectoryResult(entry = entry, localChatId = localChatId(rh, entry.link)))
|
||||
}
|
||||
}
|
||||
if (gen != generation) return
|
||||
|
||||
@@ -381,11 +381,11 @@ The search text can be sent online on request: to SimpleX Directory, to find pub
|
||||
| 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#L1016-L1021) | `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#L203-L218)) | [`runDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1009-L1014) | `searchDirectory(text)`: the directory only |
|
||||
| "Search in Directory" row ([`SearchInDirectoryRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L224-L239)) | [`runDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1009-L1014) | `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#L1006-L1007), `searchShowingSimplexLink`), and both go through [`confirmDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L328-L334), 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#L336-L364), `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`.
|
||||
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#L1006-L1007), `searchShowingSimplexLink`), and both go through [`confirmDirectorySearch()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L349-L355), 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#L357-L385), `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#L1320-L1374) and [`ChatListSearchBarWithTags`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt#L1241-L1266):
|
||||
|
||||
@@ -403,7 +403,7 @@ On desktop the rows are passed to the caller's `desktopView`, which places them
|
||||
|
||||
### OnlineSearchModel
|
||||
|
||||
**Location:** [`OnlineSearchModel`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L32-L200)
|
||||
**Location:** [`OnlineSearchModel`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L32-L221)
|
||||
|
||||
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.
|
||||
|
||||
@@ -435,24 +435,25 @@ The public state has private setters; `searchedText`, `generation` and `spinnerS
|
||||
| [`loadMore()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L114-L117) | "Show more": the next page, appended |
|
||||
| [`retry()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L120-L126) | 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#L128-L140) | The rows to render, given the ids of the local chats shown (see De-duplication) |
|
||||
| [`refreshLocalChatIds()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L142-L157) | The `cleanup` of a connection started from a result row, run when its flow ends: looks the local chat up again, store-only, for every row that has none (a name row by its target, a directory row by its link), unless the generation changed. A chat opened from a result is created during that flow, so its row is then hidden like any shown chat. Name rows are copied with `withLocalChatId`, as `NameSearchResult` keeps its constructor private |
|
||||
|
||||
**Spinner.** The private setters of `loading` and `resolvingNames` call [`updateSpinner()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L186-L199) 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#L660-L666)) 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.
|
||||
**Spinner.** The private setters of `loading` and `resolvingNames` call [`updateSpinner()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L207-L220) 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#L660-L666)) 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#L160-L184) 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#L71-L75) (`searchText`, and `searchCursor` for later pages). [`parseDirectorySearchResponse`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L79-L88) 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.
|
||||
[`request(append)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L182-L205) 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#L71-L75) (`searchText`, and `searchCursor` for later pages). [`parseDirectorySearchResponse`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L79-L88) 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-L58) keeps the wire field `groupLink` private; its [`link`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L47-L57) 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#L60-L63) 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#L90-L110), building a [`DirectoryResult`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L173-L178). 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.
|
||||
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#L90-L110), building a [`DirectoryResult`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L176-L181). 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#L142-L158) 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#L1283-L1294), 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#L112-L171) (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.
|
||||
[`resolveNames(name, gen)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L164-L180) 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#L1283-L1294), 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#L112-L174) (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#L128-L140) returns an [`OnlineSearchRows`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L180-L186) at composition time, so pages added by "Show more" are filtered too:
|
||||
[`rows(shownChatIds)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L128-L140) returns an [`OnlineSearchRows`](../../common/src/commonMain/kotlin/chat/simplex/common/model/DirectorySearch.kt#L183-L189) 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);
|
||||
@@ -475,10 +476,10 @@ Each part renders only when it has rows: the Names part when `rows.names` is not
|
||||
|
||||
| Row | View | Tap |
|
||||
|-----|------|-----|
|
||||
| Name | [`NameSearchRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L267-L295): 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#L220-L265): avatar, `#` + name, the directory's SimpleX name claim as plain text (truncates first), description (2 lines), member or subscriber count (`body2`, secondary colour), 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#L314-L326): "Search failed, tap to retry" | `withApi { retry() }` |
|
||||
| Show more | [`DirectoryShowMoreRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L297-L312): "Show more", disabled (secondary colour) while `loading` | `withApi { loadMore() }` |
|
||||
| Name | [`NameSearchRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L288-L316): 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 = { withApi { refreshLocalChatIds() } })` |
|
||||
| Directory entry | [`DirectorySearchRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L241-L286): avatar, `#` + name, the directory's SimpleX name claim as plain text (truncates first), description (2 lines), member or subscriber count (`body2`, secondary colour), and the verb "Join channel" / "Join group" (one line, never wrapped) | `planAndConnect(rhId, result.entry.link, close = null, cleanup = { withApi { refreshLocalChatIds() } })` |
|
||||
| Retry | [`DirectoryRetryRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L335-L347): "Search failed, tap to retry" | `withApi { retry() }` |
|
||||
| Show more | [`DirectoryShowMoreRow`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/DirectorySearchView.kt#L318-L333): "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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user