Skip to content

Commit 90e4de9

Browse files
committed
add no results page
1 parent f40f57c commit 90e4de9

4 files changed

Lines changed: 166 additions & 82 deletions

File tree

wordbase-android/app/src/main/java/io/github/aecsocket/wordbase/MainActivity.kt

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.activity.compose.LocalActivity
66
import androidx.activity.compose.setContent
77
import androidx.activity.enableEdgeToEdge
88
import androidx.compose.foundation.layout.Arrangement
9-
import androidx.compose.foundation.layout.Box
109
import androidx.compose.foundation.layout.Column
1110
import androidx.compose.foundation.layout.PaddingValues
1211
import androidx.compose.foundation.layout.Row
@@ -35,9 +34,7 @@ import androidx.compose.material3.SearchBarDefaults
3534
import androidx.compose.material3.Surface
3635
import androidx.compose.material3.Text
3736
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
38-
import androidx.compose.material3.rememberBottomSheetScaffoldState
3937
import androidx.compose.material3.rememberDrawerState
40-
import androidx.compose.material3.rememberStandardBottomSheetState
4138
import androidx.compose.runtime.Composable
4239
import androidx.compose.runtime.getValue
4340
import androidx.compose.runtime.mutableStateOf
@@ -207,39 +204,50 @@ fun SearchPage(
207204
insets: WindowInsets,
208205
query: String
209206
) {
210-
Column {
211-
var wordbase by rememberWordbase()
207+
var wordbase by rememberWordbase()
212208

213-
wordbase?.let { wordbase ->
214-
val activity = LocalActivity.current
215-
LookupView(
209+
wordbase?.let { wordbase ->
210+
val activity = LocalActivity.current
211+
212+
val records = rememberRecordLookup(
213+
wordbase = wordbase,
214+
profileId = 1L,
215+
sentence = query,
216+
cursor = 0UL,
217+
)
218+
219+
if (records.isEmpty()) {
220+
if (query.isNotEmpty()) {
221+
NoRecordsView()
222+
}
223+
} else {
224+
RecordsView(
216225
wordbase = wordbase,
217-
sentence = query,
218-
cursor = 0UL,
226+
records = records,
219227
insets = insets,
220228
onExit = { activity?.finish() }
221229
)
222-
} ?: run {
223-
Column(
224-
modifier = Modifier
225-
.fillMaxSize()
226-
.padding(padding)
227-
.padding(horizontal = 32.dp),
228-
horizontalAlignment = Alignment.CenterHorizontally,
229-
verticalArrangement = Arrangement.Center
230-
) {
231-
CircularProgressIndicator()
232-
233-
Text(
234-
text = stringResource(R.string.loading_title),
235-
style = MaterialTheme.typography.headlineMedium
236-
)
230+
}
231+
} ?: run {
232+
Column(
233+
modifier = Modifier
234+
.fillMaxSize()
235+
.padding(padding)
236+
.padding(horizontal = 32.dp),
237+
horizontalAlignment = Alignment.CenterHorizontally,
238+
verticalArrangement = Arrangement.Center
239+
) {
240+
CircularProgressIndicator()
237241

238-
Text(
239-
text = stringResource(R.string.loading_body),
240-
textAlign = TextAlign.Center,
241-
)
242-
}
242+
Text(
243+
text = stringResource(R.string.loading_title),
244+
style = MaterialTheme.typography.headlineMedium
245+
)
246+
247+
Text(
248+
text = stringResource(R.string.loading_body),
249+
textAlign = TextAlign.Center,
250+
)
243251
}
244252
}
245253
}

wordbase-android/app/src/main/java/io/github/aecsocket/wordbase/MiniDictionaryActivity.kt

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import androidx.activity.enableEdgeToEdge
99
import androidx.compose.foundation.horizontalScroll
1010
import androidx.compose.foundation.layout.Box
1111
import androidx.compose.foundation.layout.WindowInsets
12+
import androidx.compose.foundation.layout.aspectRatio
13+
import androidx.compose.foundation.layout.fillMaxSize
1214
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.height
1316
import androidx.compose.foundation.layout.padding
1417
import androidx.compose.foundation.layout.statusBarsPadding
1518
import androidx.compose.foundation.lazy.LazyColumn
1619
import androidx.compose.foundation.rememberScrollState
1720
import androidx.compose.foundation.text.selection.SelectionContainer
1821
import androidx.compose.material3.BottomSheetDefaults
22+
import androidx.compose.material3.CircularProgressIndicator
1923
import androidx.compose.material3.ExperimentalMaterial3Api
2024
import androidx.compose.material3.MaterialTheme
2125
import androidx.compose.material3.ModalBottomSheet
@@ -27,13 +31,13 @@ import androidx.compose.runtime.mutableStateOf
2731
import androidx.compose.runtime.remember
2832
import androidx.compose.runtime.rememberCoroutineScope
2933
import androidx.compose.runtime.setValue
34+
import androidx.compose.ui.Alignment
3035
import androidx.compose.ui.Modifier
31-
import androidx.compose.ui.graphics.Color
32-
import androidx.compose.ui.platform.LocalContext
3336
import androidx.compose.ui.platform.LocalView
3437
import androidx.compose.ui.text.LinkAnnotation
3538
import androidx.compose.ui.text.SpanStyle
3639
import androidx.compose.ui.text.buildAnnotatedString
40+
import androidx.compose.ui.text.style.TextOverflow
3741
import androidx.compose.ui.unit.dp
3842
import androidx.core.view.HapticFeedbackConstantsCompat
3943
import androidx.core.view.ViewCompat
@@ -82,6 +86,10 @@ fun MiniDictionaryUi(sentence: String) {
8286
clickable = LinkAnnotation.Clickable(
8387
tag = "",
8488
linkInteractionListener = {
89+
if (cursor.chars == thisCharIndex) {
90+
// don't trigger recomposition/re-lookup
91+
return@Clickable
92+
}
8593
cursor = Cursor(
8694
chars = thisCharIndex,
8795
bytes = thisByteIndex,
@@ -138,6 +146,7 @@ fun MiniDictionaryUi(sentence: String) {
138146
style = MaterialTheme.typography.headlineSmall,
139147
color = MaterialTheme.colorScheme.primary,
140148
softWrap = false,
149+
maxLines = 1,
141150
modifier = Modifier.padding(
142151
horizontal = 16.dp,
143152
vertical = 4.dp,
@@ -147,24 +156,43 @@ fun MiniDictionaryUi(sentence: String) {
147156
}
148157
}
149158

150-
wordbase?.let { wordbase ->
151-
item {
152-
LookupView(
159+
item {
160+
wordbase?.let { wordbase ->
161+
val records = rememberRecordLookup(
153162
wordbase = wordbase,
163+
profileId = 1L,
154164
sentence = sentence,
155165
cursor = cursor.bytes,
156-
insets = WindowInsets(0.dp),
157-
containerColor = BottomSheetDefaults.ContainerColor,
158-
onRecords = { records ->
159-
scanChars = records.maxOfOrNull { it.charsScanned } ?: 0UL
160-
},
161-
onExit = {
162-
coroutineScope.launch {
163-
sheetState.hide()
164-
activity?.finish()
165-
}
166-
},
167166
)
167+
scanChars = records.maxOfOrNull { it.charsScanned } ?: 1UL
168+
169+
if (records.isEmpty()) {
170+
NoRecordsView()
171+
} else {
172+
RecordsView(
173+
wordbase = wordbase,
174+
records = records,
175+
containerColor = BottomSheetDefaults.ContainerColor,
176+
onExit = {
177+
coroutineScope.launch {
178+
sheetState.hide()
179+
activity?.finish()
180+
}
181+
},
182+
)
183+
}
184+
} ?: run {
185+
Box(
186+
modifier = Modifier.fillMaxWidth(),
187+
contentAlignment = Alignment.Center,
188+
) {
189+
CircularProgressIndicator(
190+
modifier = Modifier
191+
.height(128.dp)
192+
.aspectRatio(1f)
193+
.padding(16.dp)
194+
)
195+
}
168196
}
169197
}
170198
}

wordbase-android/app/src/main/java/io/github/aecsocket/wordbase/LookupView.kt renamed to wordbase-android/app/src/main/java/io/github/aecsocket/wordbase/RecordsView.kt

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,68 @@
11
package io.github.aecsocket.wordbase
22

33
import androidx.activity.compose.BackHandler
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.BoxScope
46
import androidx.compose.foundation.layout.WindowInsets
57
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.padding
69
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.Text
711
import androidx.compose.material3.contentColorFor
812
import androidx.compose.runtime.Composable
913
import androidx.compose.runtime.LaunchedEffect
1014
import androidx.compose.runtime.getValue
1115
import androidx.compose.runtime.mutableStateOf
1216
import androidx.compose.runtime.remember
1317
import androidx.compose.runtime.setValue
18+
import androidx.compose.ui.Alignment
1419
import androidx.compose.ui.Modifier
1520
import androidx.compose.ui.graphics.Color
1621
import androidx.compose.ui.platform.LocalContext
1722
import androidx.compose.ui.platform.LocalDensity
1823
import androidx.compose.ui.platform.LocalLayoutDirection
24+
import androidx.compose.ui.res.stringResource
25+
import androidx.compose.ui.text.style.TextAlign
26+
import androidx.compose.ui.unit.dp
1927
import com.kevinnzou.web.WebView
2028
import com.kevinnzou.web.rememberWebViewNavigator
2129
import com.kevinnzou.web.rememberWebViewStateWithHTMLData
2230
import uniffi.wordbase.Wordbase
31+
import uniffi.wordbase_api.ProfileId
2332
import uniffi.wordbase_api.RecordKind
2433
import uniffi.wordbase_api.RecordLookup
2534

2635
@Composable
27-
fun LookupView(
36+
fun rememberRecordLookup(
2837
wordbase: Wordbase,
38+
profileId: ProfileId,
2939
sentence: String,
3040
cursor: ULong,
31-
insets: WindowInsets,
41+
): List<RecordLookup> {
42+
var records by remember { mutableStateOf(listOf<RecordLookup>()) }
43+
44+
val app = LocalContext.current.app()
45+
LaunchedEffect(arrayOf(profileId, sentence, cursor, app.dictionaries, app.profiles)) {
46+
records = wordbase.lookup(
47+
profileId = profileId,
48+
sentence = sentence,
49+
cursor = cursor,
50+
recordKinds = RecordKind.entries,
51+
)
52+
}
53+
54+
return records
55+
}
56+
57+
@Composable
58+
fun RecordsView(
59+
wordbase: Wordbase,
60+
records: List<RecordLookup>,
61+
insets: WindowInsets = WindowInsets(0.dp),
3262
containerColor: Color = MaterialTheme.colorScheme.surface,
3363
contentColor: Color = contentColorFor(containerColor),
34-
onRecords: (List<RecordLookup>) -> Unit = {},
3564
onExit: (() -> Unit)? = null,
3665
) {
37-
var html by remember { mutableStateOf<String?>(null) }
38-
3966
// amazingly, this scales perfectly
4067
val density = LocalDensity.current
4168
val layoutDir = LocalLayoutDirection.current
@@ -64,42 +91,62 @@ fun LookupView(
6491
}
6592
""".trimIndent()
6693
}
94+
val html = wordbase.renderToHtml(records) + "<style>$extraCss</style>"
6795

68-
val app = LocalContext.current.app()
69-
LaunchedEffect(arrayOf(sentence, cursor, app.dictionaries, app.profiles)) {
70-
val records = wordbase.lookup(
71-
profileId = 1L,
72-
sentence = sentence,
73-
cursor = cursor,
74-
recordKinds = RecordKind.entries,
75-
)
76-
onRecords(records)
77-
html = wordbase.renderToHtml(records) + "<style>$extraCss</style>"
96+
val webViewState = rememberWebViewStateWithHTMLData(html)
97+
val navigator = rememberWebViewNavigator()
98+
WebView(
99+
state = webViewState,
100+
navigator = navigator,
101+
modifier = Modifier.fillMaxSize(),
102+
captureBackPresses = false,
103+
onCreated = {
104+
it.settings.javaScriptEnabled = true
105+
it.settings.allowFileAccess = false
106+
it.settings.allowContentAccess = false
107+
}
108+
)
109+
110+
BackHandler {
111+
if (navigator.canGoBack) {
112+
navigator.navigateBack()
113+
} else {
114+
onExit?.invoke()
115+
}
78116
}
117+
}
79118

80-
html?.let { html ->
81-
val webViewState = rememberWebViewStateWithHTMLData(html)
82-
val navigator = rememberWebViewNavigator()
83-
WebView(
84-
state = webViewState,
85-
navigator = navigator,
86-
modifier = Modifier.fillMaxSize(),
87-
captureBackPresses = false,
88-
onCreated = {
89-
it.settings.javaScriptEnabled = true
90-
it.settings.allowFileAccess = false
91-
it.settings.allowContentAccess = false
92-
}
119+
@Composable
120+
fun NoRecordsView() {
121+
StatusPage {
122+
StatusPageTitle(
123+
text = stringResource(R.string.records_empty),
93124
)
94-
95-
BackHandler {
96-
if (navigator.canGoBack) {
97-
navigator.navigateBack()
98-
} else {
99-
onExit?.invoke()
100-
}
101-
}
102125
}
103126
}
104127

128+
@Composable
129+
fun StatusPage(content: @Composable BoxScope.() -> Unit) {
130+
Box(
131+
modifier = Modifier
132+
.fillMaxSize()
133+
.padding(
134+
horizontal = 64.dp,
135+
vertical = 96.dp,
136+
),
137+
contentAlignment = Alignment.Center,
138+
content = content,
139+
)
140+
}
141+
142+
@Composable
143+
fun StatusPageTitle(text: String) {
144+
Text(
145+
text = text,
146+
style = MaterialTheme.typography.headlineLarge,
147+
color = MaterialTheme.colorScheme.onSurfaceVariant,
148+
textAlign = TextAlign.Center,
149+
)
150+
}
151+
105152
fun Color.css() = "rgb(${red * 100}% ${green * 100}% ${blue * 100}%)"

wordbase-android/app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<string name="loading_title">Loading engine</string>
88
<string name="loading_body">If you just updated to a new version, migration may take a while.</string>
99
<string name="search_title">Search a word or phrase</string>
10+
<string name="records_empty">No results</string>
1011

1112
<string name="dictionaries">Dictionaries</string>
1213
<string name="dictionary_description">Description</string>

0 commit comments

Comments
 (0)