Skip to content

Commit 859d616

Browse files
committed
💪 改进搜索面板和键盘的配合的使用体验
现在Web3的搜索面板会自动排除掉空格; 对中间空格会用连接符做尝试
1 parent ae14c52 commit 859d616

5 files changed

Lines changed: 87 additions & 33 deletions

File tree

next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/model/BrowserViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import org.dweb_browser.helper.clamp
4646
import org.dweb_browser.helper.compose.compositionChainOf
4747
import org.dweb_browser.helper.encodeURIComponent
4848
import org.dweb_browser.helper.format
49+
import org.dweb_browser.helper.humanTrim
4950
import org.dweb_browser.helper.isDwebDeepLink
5051
import org.dweb_browser.helper.isTrimEndSlashEqual
5152
import org.dweb_browser.helper.platform.toByteArray
@@ -356,7 +357,7 @@ class BrowserViewModel(
356357
* 否:将 url 进行判断封装,符合条件后,判断当前界面是否是 BrowserWebPage,然后进行搜索操作
357358
*/
358359
fun doIOSearchUrl(searchText: String) = lifecycleScope.launch {
359-
val text = searchText.trim().trim('\u200B').trim()
360+
val text = searchText.humanTrim()
360361
if (text.isDwebDeepLink()) {
361362
browserNMM.nativeFetch(text)
362363
return@launch

next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/BrowserSearchPanel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import androidx.compose.ui.focus.focusRequester
4040
import androidx.compose.ui.graphics.SolidColor
4141
import androidx.compose.ui.platform.LocalFocusManager
4242
import androidx.compose.ui.text.input.ImeAction
43+
import androidx.compose.ui.text.input.KeyboardType
4344
import androidx.compose.ui.text.input.VisualTransformation
4445
import kotlinx.coroutines.delay
4546
import org.dweb_browser.browser.web.model.BrowserViewModel
@@ -129,7 +130,10 @@ class BrowserSearchPanel(val viewModel: BrowserViewModel) {
129130
lineLimits = TextFieldLineLimits.SingleLine,
130131
textStyle = LocalTextStyle.current.copy(color = searchFieldColors.focusedTextColor),
131132
cursorBrush = SolidColor(searchFieldColors.cursorColor),
132-
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
133+
keyboardOptions = KeyboardOptions(
134+
imeAction = ImeAction.Search,
135+
keyboardType = KeyboardType.Uri,
136+
),
133137
onKeyboardAction = {
134138
focusManager.clearFocus()
135139
suggestionActions.firstOrNull()?.invoke()

next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/SearchSuggestion.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ import androidx.compose.ui.zIndex
3737
import kotlinx.coroutines.CancellationException
3838
import kotlinx.coroutines.SupervisorJob
3939
import kotlinx.coroutines.cancel
40+
import kotlinx.coroutines.delay
4041
import kotlinx.coroutines.job
4142
import kotlinx.coroutines.launch
4243
import org.dweb_browser.browser.BrowserI18nResource
4344
import org.dweb_browser.browser.web.model.LocalBrowserViewModel
4445
import org.dweb_browser.browser.web.model.page.BrowserWebPage
46+
import org.dweb_browser.helper.humanTrim
4547

4648
internal enum class TabId {
4749
Chat,
@@ -93,17 +95,22 @@ internal fun SearchSuggestion(
9395
}
9496
DisposableEffect(searchText) {
9597
web3Searcher?.cancel(CancellationException("Cancel search"))
96-
web3Searcher = when {
97-
searchText.isEmpty() -> null
98-
else -> {
99-
val parentScope = viewModel.browserNMM.getRuntimeScope()
100-
Web3Searcher(
101-
coroutineContext = parentScope.coroutineContext + SupervisorJob(parentScope.coroutineContext.job),
102-
searchText = searchText
103-
)
98+
val job = scope.launch {
99+
delay(150)// 防抖
100+
val keyword = searchText.humanTrim()
101+
web3Searcher = when {
102+
keyword.isEmpty() -> null
103+
else -> {
104+
val parentScope = viewModel.browserNMM.getRuntimeScope()
105+
Web3Searcher(
106+
coroutineContext = parentScope.coroutineContext + SupervisorJob(parentScope.coroutineContext.job),
107+
searchText = keyword
108+
)
109+
}
104110
}
105111
}
106112
onDispose {
113+
job.cancel()
107114
web3Searcher?.cancel(CancellationException("Cancel search"))
108115
web3Searcher = null
109116
}

next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/Web3Searcher.kt

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import org.dweb_browser.core.std.dns.httpFetch
1919
import org.dweb_browser.helper.Once
2020
import org.dweb_browser.helper.commonConsumeEachArrayRange
2121
import org.dweb_browser.helper.hexString
22+
import org.dweb_browser.helper.isNoProtocolWebUrl
2223
import org.dweb_browser.helper.isWebUrl
23-
import org.dweb_browser.helper.isWebUrlOrWithoutProtocol
2424
import org.dweb_browser.helper.toWebUrl
2525
import org.dweb_browser.helper.utf8String
2626
import org.dweb_browser.pure.crypto.hash.sha256
@@ -31,8 +31,18 @@ import kotlin.coroutines.CoroutineContext
3131

3232
internal class Web3Searcher(
3333
override val coroutineContext: CoroutineContext,
34-
val searchText: String,
34+
val searchTexts: List<String>,
3535
) : CoroutineScope {
36+
constructor(coroutineContext: CoroutineContext, searchText: String) : this(
37+
coroutineContext, when {
38+
searchText.contains(' ') -> listOf(
39+
searchText.replace(Regex("\\s+"), "-"),
40+
searchText.replace(Regex("\\s+"), ""),
41+
)
42+
43+
else -> listOf(searchText)
44+
}
45+
)
3646

3747
/**
3848
* 这是新语法,如果你的IDE报错:
@@ -118,27 +128,30 @@ internal class Web3Searcher(
118128
// 创建一个 Semaphore 来限制并发数为 5
119129
val semaphore = Semaphore(5)
120130
flow {
121-
if (searchText.isWebUrl()) {
122-
emit(searchText)
123-
return@flow
124-
}
125-
if (searchText.isWebUrlOrWithoutProtocol()) {
126-
emit("https://$searchText")
127-
emit("https://dweb.$searchText")
128-
}
129-
flow {
130-
emit("com")
131-
emit("org")
132-
emit("net")
133-
}.collect { top ->
134-
emit("https://$searchText.$top")
135-
emit("https://www.$searchText.$top")
136-
emit("https://dweb.$searchText.$top")
137-
emit("https://dweb-$searchText.$top")
138-
emit("https://$searchText-dweb.$top")
131+
searchTexts.forEach { searchText ->
132+
when {
133+
searchText.isWebUrl() -> emit(searchText)
134+
searchText.isNoProtocolWebUrl() -> {
135+
emit("https://$searchText")
136+
emit("https://dweb.$searchText")
137+
}
138+
139+
else -> {
140+
flow {
141+
emit("com")
142+
emit("org")
143+
emit("net")
144+
}.collect { top ->
145+
emit("https://$searchText.$top")
146+
emit("https://www.$searchText.$top")
147+
emit("https://dweb.$searchText.$top")
148+
emit("https://dweb-$searchText.$top")
149+
emit("https://$searchText-dweb.$top")
150+
}
151+
}
152+
}
139153
}
140-
}
141-
.collect { originHref ->
154+
}.collect { originHref ->
142155
launch {
143156
semaphore.withPermit {
144157
val originUrl = originHref.toWebUrl() ?: return@withPermit
Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
11
package org.dweb_browser.helper
22

3-
public fun String.removeInvisibleChars(): String = replace(Regex("[\\p{C}\\p{Z}&&[^\\p{Zs}]]"), "")
3+
public fun String.removeInvisibleChars(): String = replace(Regex("[\\p{C}\\p{Z}&&[^\\p{Zs}]]"), "")
4+
5+
public fun String.humanTrim(): String = trim(
6+
// 零宽字符:
7+
'\u200B',//零宽空格 (ZWSP)
8+
'\u200C',//零宽非连接符 (ZWNJ)
9+
'\u200D',//零宽连接符 (ZWJ)
10+
11+
// 常见的空白字符:
12+
13+
'\u0020',// 普通空格 (Space)
14+
'\u00A0',// 不间断空格 (Non-breaking space)
15+
'\u1680',// 赡养符 (Ogham space mark)
16+
'\u2000',// 到 \u200A 一系列空格(各种宽度的空格)
17+
// 格式控制字符(这些字符主要用于控制文本格式,通常在现代文本中不再使用,但在某些场景中仍然可能出现):
18+
19+
'\u200B',// 零宽空格(Zero Width Space)
20+
'\u200C',// 零宽非连接符(Zero Width Non-Joiner)
21+
'\u200D',// 零宽连接符(Zero Width Joiner)
22+
'\u200E',// 左到右标记(Left-to-Right Mark, LRM)
23+
'\u200F',// 右到左标记(Right-to-Left Mark, RLM)
24+
'\u202A',// 到 \u202E 一些文本方向控制字符(如:右到左方向标记、强制方向标记等)
25+
// 换行符与回车符:
26+
27+
'\u000A',// 换行符(Line Feed, LF)
28+
'\u000D',// 回车符(Carriage Return, CR)
29+
'\u2028',// 行分隔符(Line Separator)
30+
'\u2029',// 段分隔符(Paragraph Separator)
31+
32+
)

0 commit comments

Comments
 (0)