Skip to content

Commit 907c088

Browse files
committed
some android app improvements
1 parent b031784 commit 907c088

8 files changed

Lines changed: 200 additions & 21 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ wordbase = { path = "crates/wordbase" }
3939
wordbase-api = { path = "crates/wordbase-api" }
4040
# wordbase-server = { path = "crates/wordbase-server" }
4141

42+
android_logger = { version = "0.15.0" }
4243
ankiconnect = { version = "0.2" }
4344
anyhow = { version = "1.0" }
4445
arc-swap = { version = "1.7" }
@@ -64,6 +65,7 @@ genawaiter = { version = "0.99" }
6465
gio = { version = "0.20.9" }
6566
glib = { version = "0.20" }
6667
gtk4 = { version = "0.9" }
68+
hex = { version = "0.4" }
6769
html-escape = { version = "0.2" }
6870
indexmap = { version = "2.8" }
6971
itertools = { version = "0.14" }
@@ -91,6 +93,7 @@ rustyline = { version = "15.0" }
9193
serde = { version = "1.0" }
9294
serde_json = { version = "1.0" }
9395
serde_repr = { version = "0.1" }
96+
sha2 = { version = "0.10" }
9497
sqlx = { version = "0.8" }
9598
tempfile = { version = "3.20" }
9699
tera = { version = "1.20" }
@@ -106,9 +109,6 @@ webkit6 = { version = "0.4" }
106109
xz2 = { version = "0.1" }
107110
zbus = { version = "5.5", default-features = false }
108111
zip = { version = "4.0" }
109-
android_logger = { version = "0.15.0"}
110-
sha2 = { version = "0.10"}
111-
hex = { version = "0.4" }
112112

113113
[workspace.metadata.cargo-shear]
114114
ignored = ["bzip2"]

crates/wordbase/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ workspace = true
1818
all-features = true
1919

2020
[features]
21-
poem = ["wordbase-api/poem"]
22-
uniffi = ["wordbase-api/uniffi", "dep:uniffi"]
21+
poem = ["wordbase-api/poem"]
22+
uniffi = ["wordbase-api/uniffi", "dep:uniffi"]
2323

24-
desktop = ["dep:directories", "dep:tokio-tungstenite"]
2524
android = ["dep:android_logger", "tracing/log-always"]
25+
desktop = ["dep:directories", "dep:tokio-tungstenite"]
2626

2727
[dependencies]
2828
jmdict-furigana = { workspace = true }
@@ -41,6 +41,7 @@ directories = { workspace = true, optional = true }
4141
distance = { workspace = true }
4242
foldhash = { workspace = true }
4343
futures = { workspace = true }
44+
hex = { workspace = true }
4445
indexmap = { workspace = true, features = ["serde"] }
4546
itertools = { workspace = true }
4647
lindera = { workspace = true, features = ["unidic", "compress"] }
@@ -52,6 +53,7 @@ rmp-serde = { workspace = true }
5253
serde = { workspace = true, features = ["derive"] }
5354
serde_json = { workspace = true, features = ["preserve_order"] }
5455
serde_repr = { workspace = true }
56+
sha2 = { workspace = true }
5557
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite"] }
5658
tera = { workspace = true, features = ["preserve_order"] }
5759
tokio = { workspace = true, features = ["sync", "macros"] }
@@ -61,8 +63,6 @@ tokio-util = { workspace = true, features = ["rt"] }
6163
tracing = { workspace = true }
6264
unicode-segmentation = { workspace = true }
6365
uniffi = { workspace = true, optional = true, features = ["tokio"] }
64-
sha2 = { workspace = true }
65-
hex = { workspace = true }
6666

6767
derive_more = { workspace = true, features = [
6868
"debug",

crates/wordbase/src/anki.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,13 @@ fn frequency_list<'a>(
228228
.iter()
229229
.filter_map(|entry| match &entry.record {
230230
Record::YomitanFrequency(dict::yomitan::Frequency { value, display }) => {
231-
match (value, display) {
232-
(_, Some(display)) => Some((entry, display.clone())),
233-
(Some(FrequencyValue::Rank(rank)), None) => Some((entry, format!("{rank}"))),
234-
_ => None,
231+
match (display, value) {
232+
(Some(display), _) => Some((entry, display.clone())),
233+
(None, Some(FrequencyValue::Rank(rank))) => Some((entry, format!("{rank} ↓"))),
234+
(None, Some(FrequencyValue::Occurrence(occurrence))) => {
235+
Some((entry, format!("{occurrence} ↑")))
236+
}
237+
(None, None) => None,
235238
}
236239
}
237240
_ => None,

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
taplo
5353
cargo-shear
5454
pkg-config
55+
56+
# Wordbase
5557
openssl
5658
sqlx-cli
5759
sqlite

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
1717
import androidx.compose.foundation.layout.navigationBarsPadding
1818
import androidx.compose.foundation.layout.padding
1919
import androidx.compose.foundation.layout.statusBarsPadding
20+
import androidx.compose.foundation.text.KeyboardOptions
2021
import androidx.compose.material.icons.Icons
2122
import androidx.compose.material.icons.filled.Clear
2223
import androidx.compose.material.icons.filled.Menu
2324
import androidx.compose.material.icons.filled.Search
2425
import androidx.compose.material3.BottomSheetScaffold
26+
import androidx.compose.material3.Button
2527
import androidx.compose.material3.CircularProgressIndicator
2628
import androidx.compose.material3.DrawerValue
2729
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -31,21 +33,26 @@ import androidx.compose.material3.MaterialTheme
3133
import androidx.compose.material3.ModalDrawerSheet
3234
import androidx.compose.material3.ModalNavigationDrawer
3335
import androidx.compose.material3.SearchBar
34-
import androidx.compose.material3.SearchBarDefaults
3536
import androidx.compose.material3.Surface
3637
import androidx.compose.material3.Text
3738
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
3839
import androidx.compose.material3.rememberDrawerState
3940
import androidx.compose.runtime.Composable
41+
import androidx.compose.runtime.LaunchedEffect
4042
import androidx.compose.runtime.getValue
4143
import androidx.compose.runtime.mutableStateOf
44+
import androidx.compose.runtime.remember
4245
import androidx.compose.runtime.rememberCoroutineScope
4346
import androidx.compose.runtime.saveable.rememberSaveable
4447
import androidx.compose.runtime.setValue
4548
import androidx.compose.ui.Alignment
4649
import androidx.compose.ui.Modifier
50+
import androidx.compose.ui.focus.FocusRequester
4751
import androidx.compose.ui.platform.LocalLayoutDirection
4852
import androidx.compose.ui.res.stringResource
53+
import androidx.compose.ui.text.input.ImeAction
54+
import androidx.compose.ui.text.input.KeyboardType
55+
import androidx.compose.ui.text.intl.LocaleList
4956
import androidx.compose.ui.text.style.TextAlign
5057
import androidx.compose.ui.tooling.preview.Preview
5158
import androidx.compose.ui.unit.dp
@@ -94,8 +101,9 @@ fun AppUi() {
94101
fun Ui(manageContent: @Composable (Modifier) -> Unit) {
95102
var query by rememberSaveable { mutableStateOf("") }
96103

104+
val searchFocusRequester = remember { FocusRequester() }
97105
val inputField = @Composable {
98-
SearchBarDefaults.InputField(
106+
SearchBarInputField(
99107
query = query,
100108
onQueryChange = { query = it },
101109
onSearch = {},
@@ -121,10 +129,20 @@ fun Ui(manageContent: @Composable (Modifier) -> Unit) {
121129
)
122130
}
123131
}
124-
}
132+
},
133+
focusRequester = searchFocusRequester,
134+
keyboardOptions = KeyboardOptions(
135+
imeAction = ImeAction.Search,
136+
keyboardType = KeyboardType.Text,
137+
hintLocales = LocaleList("ja-JP"), // TODO
138+
),
125139
)
126140
}
127141

142+
LaunchedEffect(Unit) {
143+
searchFocusRequester.requestFocus()
144+
}
145+
128146
if (currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
129147
BottomSheetScaffold(
130148
sheetContent = {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,5 +942,3 @@ fun ExpanderCard(
942942
}
943943
}
944944
}
945-
946-
//class ImportWorker(appContext: Context, workerParams: WorkerP)

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,6 @@ fun Term.asString(): String {
419419

420420
fun Term.displayString() = headword?.let { headword ->
421421
reading?.let { reading ->
422-
"($headword, $reading)"
422+
"$headword ($reading)"
423423
} ?: headword
424-
} ?: reading?.let { reading ->
425-
"(-, $reading)"
426-
} ?: "-"
424+
} ?: reading ?: "?"
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package io.github.aecsocket.wordbase
2+
3+
import androidx.compose.foundation.interaction.MutableInteractionSource
4+
import androidx.compose.foundation.interaction.collectIsFocusedAsState
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.offset
7+
import androidx.compose.foundation.layout.sizeIn
8+
import androidx.compose.foundation.text.BasicTextField
9+
import androidx.compose.foundation.text.KeyboardActions
10+
import androidx.compose.foundation.text.KeyboardOptions
11+
import androidx.compose.material3.ExperimentalMaterial3Api
12+
import androidx.compose.material3.LocalTextStyle
13+
import androidx.compose.material3.SearchBarDefaults
14+
import androidx.compose.material3.SearchBarDefaults.InputFieldHeight
15+
import androidx.compose.material3.SearchBarDefaults.inputFieldColors
16+
import androidx.compose.material3.TextFieldColors
17+
import androidx.compose.material3.TextFieldDefaults
18+
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.LaunchedEffect
20+
import androidx.compose.runtime.Stable
21+
import androidx.compose.runtime.remember
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.focus.FocusRequester
24+
import androidx.compose.ui.focus.focusRequester
25+
import androidx.compose.ui.focus.onFocusChanged
26+
import androidx.compose.ui.graphics.Color
27+
import androidx.compose.ui.graphics.SolidColor
28+
import androidx.compose.ui.graphics.takeOrElse
29+
import androidx.compose.ui.platform.LocalFocusManager
30+
import androidx.compose.ui.semantics.onClick
31+
import androidx.compose.ui.semantics.semantics
32+
import androidx.compose.ui.text.TextStyle
33+
import androidx.compose.ui.text.input.VisualTransformation
34+
import androidx.compose.ui.unit.Dp
35+
import androidx.compose.ui.unit.dp
36+
import kotlinx.coroutines.delay
37+
38+
@ExperimentalMaterial3Api
39+
@Composable
40+
fun SearchBarInputField(
41+
query: String,
42+
onQueryChange: (String) -> Unit,
43+
onSearch: (String) -> Unit,
44+
expanded: Boolean,
45+
onExpandedChange: (Boolean) -> Unit,
46+
modifier: Modifier = Modifier,
47+
enabled: Boolean = true,
48+
placeholder: @Composable (() -> Unit)? = null,
49+
leadingIcon: @Composable (() -> Unit)? = null,
50+
trailingIcon: @Composable (() -> Unit)? = null,
51+
colors: TextFieldColors = inputFieldColors(),
52+
interactionSource: MutableInteractionSource? = null,
53+
// new
54+
keyboardOptions: KeyboardOptions = KeyboardOptions(),
55+
focusRequester: FocusRequester = remember { FocusRequester() },
56+
) {
57+
@Suppress("NAME_SHADOWING")
58+
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
59+
60+
val focused = interactionSource.collectIsFocusedAsState().value
61+
// val focusRequester = remember { FocusRequester() }
62+
val focusManager = LocalFocusManager.current
63+
64+
// val searchSemantics = getString(Strings.SearchBarSearch)
65+
// val suggestionsAvailableSemantics = getString(Strings.SuggestionsAvailable)
66+
67+
val textColor =
68+
LocalTextStyle.current.color.takeOrElse {
69+
colors.textColor(enabled, isError = false, focused = focused)
70+
}
71+
72+
BasicTextField(
73+
value = query,
74+
onValueChange = onQueryChange,
75+
modifier =
76+
modifier
77+
.sizeIn(
78+
minWidth = SearchBarMinWidth,
79+
maxWidth = SearchBarMaxWidth,
80+
minHeight = InputFieldHeight,
81+
)
82+
.focusRequester(focusRequester)
83+
.onFocusChanged { if (it.isFocused) onExpandedChange(true) }
84+
.semantics {
85+
// contentDescription = searchSemantics
86+
// if (expanded) {
87+
// stateDescription = suggestionsAvailableSemantics
88+
// }
89+
onClick {
90+
focusRequester.requestFocus()
91+
true
92+
}
93+
},
94+
enabled = enabled,
95+
singleLine = true,
96+
textStyle = LocalTextStyle.current.merge(TextStyle(color = textColor)),
97+
cursorBrush = SolidColor(colors.cursorColor(isError = false)),
98+
keyboardOptions = keyboardOptions, // patched
99+
keyboardActions = KeyboardActions(onSearch = { onSearch(query) }),
100+
interactionSource = interactionSource,
101+
decorationBox =
102+
@Composable { innerTextField ->
103+
TextFieldDefaults.DecorationBox(
104+
value = query,
105+
innerTextField = innerTextField,
106+
enabled = enabled,
107+
singleLine = true,
108+
visualTransformation = VisualTransformation.None,
109+
interactionSource = interactionSource,
110+
placeholder = placeholder,
111+
leadingIcon =
112+
leadingIcon?.let { leading ->
113+
{ Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() } }
114+
},
115+
trailingIcon =
116+
trailingIcon?.let { trailing ->
117+
{ Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() } }
118+
},
119+
shape = SearchBarDefaults.inputFieldShape,
120+
colors = colors,
121+
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(),
122+
container = {},
123+
)
124+
}
125+
)
126+
127+
val shouldClearFocus = !expanded && focused
128+
LaunchedEffect(expanded) {
129+
if (shouldClearFocus) {
130+
// Not strictly needed according to the motion spec, but since the animation
131+
// already has a delay, this works around b/261632544.
132+
delay(AnimationDelayMillis.toLong())
133+
focusManager.clearFocus()
134+
}
135+
}
136+
}
137+
138+
private val SearchBarMinWidth: Dp = 360.dp
139+
private val SearchBarMaxWidth: Dp = 720.dp
140+
private val SearchBarIconOffsetX: Dp = 4.dp
141+
142+
private const val DurationShort2 = 100.0
143+
private const val AnimationDelayMillis: Int = DurationShort2.toInt()
144+
145+
@Stable
146+
private fun TextFieldColors.textColor(
147+
enabled: Boolean,
148+
isError: Boolean,
149+
focused: Boolean,
150+
): Color =
151+
when {
152+
!enabled -> disabledTextColor
153+
isError -> errorTextColor
154+
focused -> focusedTextColor
155+
else -> unfocusedTextColor
156+
}
157+
158+
@Stable
159+
private fun TextFieldColors.cursorColor(isError: Boolean): Color =
160+
if (isError) errorCursorColor else cursorColor

0 commit comments

Comments
 (0)