Implement soft keyboard visibility on Android
- Added `SoftKeyboardContext` enum and support for calculating keyboard insets from both virtual and platform sources - Updated `AppContext` to provide `soft_keyboard_rect` for determining visible keyboard area - Adjusted UI rendering to shift content when input boxes intersect with the soft keyboard, preventing overlap - Modified `MainActivity` and Android manifest to use `windowSoftInputMode="adjustResize"` and updated window inset handling - Introduced helper functions (`include_input`, `input_rect`, `clear_input_rect`) in `notedeck_ui` for tracking focused input boxes - Fixed Android JNI keyboard height reporting to clamp negative values Together, these changes allow the app to correctly detect and respond to soft keyboard visibility on Android, ensuring input fields remain accessible when typing. Fixes: https://github.com/damus-io/notedeck/issues/946 Fixes: https://github.com/damus-io/notedeck/issues/1043
This commit is contained in:
40
Cargo.lock
generated
40
Cargo.lock
generated
@@ -105,7 +105,8 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "android-activity"
|
name = "android-activity"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/damus-io/android-activity?rev=a8948332c7c551303d32eb26a59d0abd676e47a5#a8948332c7c551303d32eb26a59d0abd676e47a5"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-properties",
|
"android-properties",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
@@ -125,7 +126,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "android-activity"
|
name = "android-activity"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/damus-io/android-activity?rev=c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9#c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9"
|
source = "git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea#092a83b747937a2890ac219617a4252c001842ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-properties",
|
"android-properties",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
@@ -1402,7 +1403,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dpi"
|
name = "dpi"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "git+https://github.com/damus-io/winit?rev=eaff639ab0a14fccf595241f687be883154b267c#eaff639ab0a14fccf595241f687be883154b267c"
|
source = "git+https://github.com/damus-io/winit?rev=9e4ea9de75222d2523a20f18d3a0a108c573737d#9e4ea9de75222d2523a20f18d3a0a108c573737d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dpi"
|
name = "dpi"
|
||||||
@@ -1419,17 +1420,17 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ecolor"
|
name = "ecolor"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
|
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1465,13 +1466,13 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "egui"
|
name = "egui"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"ahash",
|
"ahash",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
|
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)",
|
||||||
"epaint",
|
"epaint",
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
@@ -1483,7 +1484,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1502,7 +1503,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-winit"
|
name = "egui-winit"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"arboard",
|
"arboard",
|
||||||
@@ -1520,7 +1521,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_extras"
|
name = "egui_extras"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"egui",
|
"egui",
|
||||||
@@ -1537,7 +1538,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1616,7 +1617,7 @@ checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1714,13 +1715,13 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"ecolor",
|
"ecolor",
|
||||||
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
|
"emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)",
|
||||||
"epaint_default_fonts",
|
"epaint_default_fonts",
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
@@ -1732,7 +1733,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint_default_fonts"
|
name = "epaint_default_fonts"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
|
source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equator"
|
name = "equator"
|
||||||
@@ -3506,6 +3507,7 @@ dependencies = [
|
|||||||
name = "notedeck"
|
name = "notedeck"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea)",
|
||||||
"base32",
|
"base32",
|
||||||
"bech32",
|
"bech32",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -7445,10 +7447,10 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "winit"
|
name = "winit"
|
||||||
version = "0.30.8"
|
version = "0.30.8"
|
||||||
source = "git+https://github.com/damus-io/winit?rev=eaff639ab0a14fccf595241f687be883154b267c#eaff639ab0a14fccf595241f687be883154b267c"
|
source = "git+https://github.com/damus-io/winit?rev=9e4ea9de75222d2523a20f18d3a0a108c573737d#9e4ea9de75222d2523a20f18d3a0a108c573737d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9)",
|
"android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea)",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
@@ -7500,7 +7502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4"
|
checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=a8948332c7c551303d32eb26a59d0abd676e47a5)",
|
"android-activity 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -88,7 +88,7 @@ openai-api-rs = "6.0.3"
|
|||||||
re_memory = "0.23.4"
|
re_memory = "0.23.4"
|
||||||
oot_bitset = "0.1.1"
|
oot_bitset = "0.1.1"
|
||||||
blurhash = "0.2.3"
|
blurhash = "0.2.3"
|
||||||
|
android-activity = { git = "https://github.com/damus-io/android-activity", rev = "092a83b747937a2890ac219617a4252c001842ea", features = [ "game-activity" ] }
|
||||||
|
|
||||||
[profile.small]
|
[profile.small]
|
||||||
inherits = 'release'
|
inherits = 'release'
|
||||||
@@ -106,15 +106,15 @@ strip = true # Strip symbols from binary*
|
|||||||
#egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" }
|
#egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" }
|
||||||
#epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" }
|
#epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" }
|
||||||
|
|
||||||
egui = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
egui = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
eframe = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
eframe = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
egui-winit = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
egui-winit = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
egui_extras = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
egui_extras = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
epaint = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
|
epaint = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" }
|
||||||
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
||||||
puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
||||||
#winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" }
|
#winit = { git = "https://github.com/damus-io/winit", rev = "701a43d3c6479b0a3869acd2cebbfd410d399a59" }
|
||||||
#winit = { path = "/home/jb55/dev/github/rust-windowing/winit" }
|
#winit = { path = "/home/jb55/dev/github/rust-windowing/winit" }
|
||||||
android-activity = { git = "https://github.com/damus-io/android-activity", rev = "a8948332c7c551303d32eb26a59d0abd676e47a5" }
|
#android-activity = { git = "https://github.com/damus-io/android-activity", rev = "f56c974aa5182d5fbd361879f5899eb8f11a37ec" }
|
||||||
#android-activity = { path = "/home/jb55/dev/github/rust-mobile/android-activity/android-activity" }
|
#android-activity = { path = "/home/jb55/dev/github/rust-mobile/android-activity/android-activity" }
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -27,4 +27,4 @@ push-android-config:
|
|||||||
android: jni
|
android: jni
|
||||||
cd $(ANDROID_DIR) && ./gradlew installDebug
|
cd $(ANDROID_DIR) && ./gradlew installDebug
|
||||||
adb shell am start -n com.damus.notedeck/.MainActivity
|
adb shell am start -n com.damus.notedeck/.MainActivity
|
||||||
adb logcat -v color -s RustStdoutStderr -s threaded_app | tee logcat.txt
|
adb logcat -v color -s GameActivity -s RustStdoutStderr -s threaded_app | tee logcat.txt
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ tokio = { workspace = true }
|
|||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
jni = { workspace = true }
|
jni = { workspace = true }
|
||||||
|
android-activity = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
puffin = ["puffin_egui", "dep:puffin"]
|
puffin = ["puffin_egui", "dep:puffin"]
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ use std::rc::Rc;
|
|||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};
|
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
use android_activity::AndroidApp;
|
||||||
|
|
||||||
pub enum AppAction {
|
pub enum AppAction {
|
||||||
Note(NoteAction),
|
Note(NoteAction),
|
||||||
ToggleChrome,
|
ToggleChrome,
|
||||||
@@ -51,6 +54,9 @@ pub struct Notedeck {
|
|||||||
frame_history: FrameHistory,
|
frame_history: FrameHistory,
|
||||||
job_pool: JobPool,
|
job_pool: JobPool,
|
||||||
i18n: Localization,
|
i18n: Localization,
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
android_app: Option<AndroidApp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Our chrome, which is basically nothing
|
/// Our chrome, which is basically nothing
|
||||||
@@ -138,6 +144,11 @@ fn setup_puffin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Notedeck {
|
impl Notedeck {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn set_android_context(&mut self, context: AndroidApp) {
|
||||||
|
self.android_app = Some(context);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self {
|
pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
setup_puffin();
|
setup_puffin();
|
||||||
@@ -272,6 +283,8 @@ impl Notedeck {
|
|||||||
zaps,
|
zaps,
|
||||||
job_pool,
|
job_pool,
|
||||||
i18n,
|
i18n,
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
android_app: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +348,8 @@ impl Notedeck {
|
|||||||
frame_history: &mut self.frame_history,
|
frame_history: &mut self.frame_history,
|
||||||
job_pool: &mut self.job_pool,
|
job_pool: &mut self.job_pool,
|
||||||
i18n: &mut self.i18n,
|
i18n: &mut self.i18n,
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
android: self.android_app.as_ref().unwrap().clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use egui_winit::clipboard::Clipboard;
|
|||||||
use enostr::RelayPool;
|
use enostr::RelayPool;
|
||||||
use nostrdb::Ndb;
|
use nostrdb::Ndb;
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
use android_activity::AndroidApp;
|
||||||
|
use egui::{Pos2, Rect};
|
||||||
// TODO: make this interface more sandboxed
|
// TODO: make this interface more sandboxed
|
||||||
|
|
||||||
pub struct AppContext<'a> {
|
pub struct AppContext<'a> {
|
||||||
@@ -26,4 +29,49 @@ pub struct AppContext<'a> {
|
|||||||
pub frame_history: &'a mut FrameHistory,
|
pub frame_history: &'a mut FrameHistory,
|
||||||
pub job_pool: &'a mut JobPool,
|
pub job_pool: &'a mut JobPool,
|
||||||
pub i18n: &'a mut Localization,
|
pub i18n: &'a mut Localization,
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub android: AndroidApp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SoftKeyboardContext {
|
||||||
|
Virtual,
|
||||||
|
Platform { ppp: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AppContext<'a> {
|
||||||
|
pub fn soft_keyboard_rect(&self, screen_rect: Rect, ctx: SoftKeyboardContext) -> Option<Rect> {
|
||||||
|
match ctx {
|
||||||
|
SoftKeyboardContext::Virtual => {
|
||||||
|
let height = 400.0;
|
||||||
|
skb_rect_from_screen_rect(screen_rect, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
SoftKeyboardContext::Platform { ppp } => {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
{
|
||||||
|
use android_activity::InsetType;
|
||||||
|
let inset = self.android.get_window_insets(InsetType::Ime);
|
||||||
|
let height = inset.bottom as f32 / ppp;
|
||||||
|
skb_rect_from_screen_rect(screen_rect, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn skb_rect_from_screen_rect(screen_rect: Rect, height: f32) -> Option<Rect> {
|
||||||
|
if height == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let min = Pos2::new(0.0, screen_rect.max.y - height);
|
||||||
|
Some(Rect::from_min_max(min, screen_rect.max))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub use account::relay::RelayAction;
|
|||||||
pub use account::FALLBACK_PUBKEY;
|
pub use account::FALLBACK_PUBKEY;
|
||||||
pub use app::{App, AppAction, Notedeck};
|
pub use app::{App, AppAction, Notedeck};
|
||||||
pub use args::Args;
|
pub use args::Args;
|
||||||
pub use context::AppContext;
|
pub use context::{AppContext, SoftKeyboardContext};
|
||||||
pub use error::{show_one_error_message, Error, FilterError, ZapError};
|
pub use error::{show_one_error_message, Error, FilterError, ZapError};
|
||||||
pub use filter::{FilterState, FilterStates, UnifiedSubscription};
|
pub use filter::{FilterState, FilterStates, UnifiedSubscription};
|
||||||
pub use fonts::NamedFontFamily;
|
pub use fonts::NamedFontFamily;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub extern "C" fn Java_com_damus_notedeck_KeyboardHeightHelper_nativeKeyboardHei
|
|||||||
debug!("updating virtual keyboard height {}", height);
|
debug!("updating virtual keyboard height {}", height);
|
||||||
|
|
||||||
// Convert and store atomically
|
// Convert and store atomically
|
||||||
KEYBOARD_HEIGHT.store(height, Ordering::SeqCst);
|
KEYBOARD_HEIGHT.store(height.max(0), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current Android virtual keyboard height. Useful for transforming
|
/// Gets the current Android virtual keyboard height. Useful for transforming
|
||||||
|
|||||||
@@ -20,3 +20,13 @@ pub fn virtual_keyboard_height(virt: bool) -> i32 {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn virtual_keyboard_rect(ui: &egui::Ui, virt: bool) -> Option<egui::Rect> {
|
||||||
|
let height = virtual_keyboard_height(virt);
|
||||||
|
if height <= 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let screen_rect = ui.ctx().screen_rect();
|
||||||
|
let min = egui::Pos2::new(0.0, screen_rect.max.y - height as f32);
|
||||||
|
Some(egui::Rect::from_min_max(min, screen_rect.max))
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|fontScale|smallestScreenSize"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|fontScale|smallestScreenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
>
|
>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ public class MainActivity extends GameActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
// Shrink view so it does not get covered by insets.
|
// Shrink view so it does not get covered by insets.
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setupInsets();
|
setupInsets();
|
||||||
//setupFullscreen()
|
|
||||||
keyboardHelper = new KeyboardHeightHelper(this);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
//setupFullscreen()
|
||||||
|
|
||||||
|
//keyboardHelper = new KeyboardHeightHelper(this);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFullscreen() {
|
private void setupFullscreen() {
|
||||||
@@ -61,6 +64,18 @@ public class MainActivity extends GameActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupInsets() {
|
private void setupInsets() {
|
||||||
|
|
||||||
|
// NOTE(jb55): This is needed for keyboard visibility. Without this the
|
||||||
|
// window still gets the right insets, but they’re consumed before they
|
||||||
|
// reach the NDK side.
|
||||||
|
//WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
|
||||||
|
// NOTE(jb55): This is needed for keyboard visibility. If the bars are
|
||||||
|
// permanently gone, Android routes the keyboard over the GL surface and
|
||||||
|
// doesn’t change insets.
|
||||||
|
//WindowInsetsControllerCompat ic = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
|
||||||
|
//ic.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||||
|
|
||||||
View content = getContent();
|
View content = getContent();
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
|
||||||
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
@@ -72,12 +87,13 @@ public class MainActivity extends GameActivity {
|
|||||||
mlp.rightMargin = insets.right;
|
mlp.rightMargin = insets.right;
|
||||||
v.setLayoutParams(mlp);
|
v.setLayoutParams(mlp);
|
||||||
|
|
||||||
return WindowInsetsCompat.CONSUMED;
|
return windowInsets;
|
||||||
});
|
});
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -95,6 +111,7 @@ public class MainActivity extends GameActivity {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
keyboardHelper.close();
|
keyboardHelper.close();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub async fn android_main(app: AndroidApp) {
|
|||||||
//std::env::set_var("DAVE_MODEL", "hhao/qwen2.5-coder-tools:latest");
|
//std::env::set_var("DAVE_MODEL", "hhao/qwen2.5-coder-tools:latest");
|
||||||
std::env::set_var(
|
std::env::set_var(
|
||||||
"RUST_LOG",
|
"RUST_LOG",
|
||||||
"egui=debug,egui-winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug",
|
"egui=debug,egui-winit=debug,winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug",
|
||||||
);
|
);
|
||||||
|
|
||||||
//std::env::set_var(
|
//std::env::set_var(
|
||||||
@@ -57,7 +57,7 @@ pub async fn android_main(app: AndroidApp) {
|
|||||||
|
|
||||||
options.android_app = Some(app.clone());
|
options.android_app = Some(app.clone());
|
||||||
|
|
||||||
let app_args = get_app_args(app);
|
let app_args = get_app_args(app.clone());
|
||||||
|
|
||||||
let _res = eframe::run_native(
|
let _res = eframe::run_native(
|
||||||
"Damus Notedeck",
|
"Damus Notedeck",
|
||||||
@@ -65,6 +65,7 @@ pub async fn android_main(app: AndroidApp) {
|
|||||||
Box::new(move |cc| {
|
Box::new(move |cc| {
|
||||||
let ctx = &cc.egui_ctx;
|
let ctx = &cc.egui_ctx;
|
||||||
let mut notedeck = Notedeck::new(ctx, path, &app_args);
|
let mut notedeck = Notedeck::new(ctx, path, &app_args);
|
||||||
|
notedeck.set_android_context(app.clone());
|
||||||
notedeck.setup(ctx);
|
notedeck.setup(ctx);
|
||||||
let chrome = Chrome::new_with_apps(cc, &app_args, &mut notedeck)?;
|
let chrome = Chrome::new_with_apps(cc, &app_args, &mut notedeck)?;
|
||||||
notedeck.set_app(chrome);
|
notedeck.set_app(chrome);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference
|
|||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
use nostrdb::{ProfileRecord, Transaction};
|
use nostrdb::{ProfileRecord, Transaction};
|
||||||
use notedeck::Error;
|
use notedeck::Error;
|
||||||
|
use notedeck::SoftKeyboardContext;
|
||||||
use notedeck::{
|
use notedeck::{
|
||||||
tr, App, AppAction, AppContext, Localization, Notedeck, NotedeckOptions, NotedeckTextStyle,
|
tr, App, AppAction, AppContext, Localization, Notedeck, NotedeckOptions, NotedeckTextStyle,
|
||||||
UserAccount, WalletType,
|
UserAccount, WalletType,
|
||||||
@@ -267,9 +268,40 @@ impl Chrome {
|
|||||||
let amt_open = self.amount_open(ui);
|
let amt_open = self.amount_open(ui);
|
||||||
let r = self.panel(ctx, StripBuilder::new(ui), amt_open);
|
let r = self.panel(ctx, StripBuilder::new(ui), amt_open);
|
||||||
|
|
||||||
// virtual keyboard
|
let skb_ctx = if self.options.contains(ChromeOptions::VirtualKeyboard) {
|
||||||
if self.options.contains(ChromeOptions::VirtualKeyboard) {
|
SoftKeyboardContext::Virtual
|
||||||
virtual_keyboard_ui(ui);
|
} else {
|
||||||
|
SoftKeyboardContext::Platform {
|
||||||
|
ppp: ui.ctx().pixels_per_point(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// move screen up if virtual keyboard intersects with input_rect
|
||||||
|
let screen_rect = ui.ctx().screen_rect();
|
||||||
|
let mut keyboard_height = 0.0;
|
||||||
|
if let Some(vkb_rect) = ctx.soft_keyboard_rect(screen_rect, skb_ctx.clone()) {
|
||||||
|
if let SoftKeyboardContext::Virtual = skb_ctx {
|
||||||
|
virtual_keyboard_ui(ui, vkb_rect);
|
||||||
|
}
|
||||||
|
if let Some(input_rect) = notedeck_ui::input_rect(ui) {
|
||||||
|
if input_rect.intersects(vkb_rect) {
|
||||||
|
tracing::debug!("screen:{screen_rect} skb:{vkb_rect}");
|
||||||
|
keyboard_height = vkb_rect.height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// clear last input box position state
|
||||||
|
notedeck_ui::clear_input_rect(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
let anim_height =
|
||||||
|
ui.ctx()
|
||||||
|
.animate_value_with_time(egui::Id::new("keyboard_anim"), keyboard_height, 0.1);
|
||||||
|
if anim_height > 0.0 {
|
||||||
|
ui.ctx().transform_layer_shapes(
|
||||||
|
ui.layer_id(),
|
||||||
|
egui::emath::TSTransform::from_translation(egui::Vec2::new(0.0, -anim_height)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
r
|
r
|
||||||
@@ -912,12 +944,7 @@ fn repaint_causes_window(ui: &mut egui::Ui, causes: &HashMap<egui::RepaintCause,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn virtual_keyboard_ui(ui: &mut egui::Ui) {
|
fn virtual_keyboard_ui(ui: &mut egui::Ui, rect: egui::Rect) {
|
||||||
let height = notedeck::platform::virtual_keyboard_height(true);
|
|
||||||
let screen_rect = ui.ctx().screen_rect();
|
|
||||||
|
|
||||||
let min = egui::Pos2::new(0.0, screen_rect.max.y - height as f32);
|
|
||||||
let rect = Rect::from_min_max(min, screen_rect.max);
|
|
||||||
let painter = ui.painter_at(rect);
|
let painter = ui.painter_at(rect);
|
||||||
|
|
||||||
painter.rect_filled(rect, 0.0, Color32::from_black_alpha(200));
|
painter.rect_filled(rect, 0.0, Color32::from_black_alpha(200));
|
||||||
|
|||||||
@@ -64,18 +64,15 @@ pub struct Damus {
|
|||||||
pub onboarding: Onboarding,
|
pub onboarding: Onboarding,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key_events(input: &egui::InputState, columns: &mut Columns) {
|
fn handle_egui_events(input: &egui::InputState, columns: &mut Columns) {
|
||||||
for event in &input.raw.events {
|
for event in &input.raw.events {
|
||||||
#[allow(clippy::collapsible_match)]
|
match event {
|
||||||
if let egui::Event::Key {
|
egui::Event::Key { key, pressed, .. } if *pressed => match key {
|
||||||
key, pressed: true, ..
|
|
||||||
} = event
|
|
||||||
{
|
|
||||||
match key {
|
|
||||||
/*
|
|
||||||
egui::Key::J => {
|
egui::Key::J => {
|
||||||
columns.select_down();
|
//columns.select_down();
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
egui::Key::K => {
|
egui::Key::K => {
|
||||||
columns.select_up();
|
columns.select_up();
|
||||||
}
|
}
|
||||||
@@ -90,7 +87,13 @@ fn handle_key_events(input: &egui::InputState, columns: &mut Columns) {
|
|||||||
columns.get_selected_router().go_back();
|
columns.get_selected_router().go_back();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
egui::Event::InsetsChanged => {
|
||||||
|
tracing::debug!("insets have changed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +105,7 @@ fn try_process_event(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let current_columns =
|
let current_columns =
|
||||||
get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, &mut damus.decks_cache);
|
get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, &mut damus.decks_cache);
|
||||||
ctx.input(|i| handle_key_events(i, current_columns));
|
ctx.input(|i| handle_egui_events(i, current_columns));
|
||||||
|
|
||||||
let ctx2 = ctx.clone();
|
let ctx2 = ctx.clone();
|
||||||
let wakeup = move || {
|
let wakeup = move || {
|
||||||
|
|||||||
@@ -832,7 +832,7 @@ fn render_nav_body(
|
|||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
|
||||||
if EditProfileView::new(ctx.i18n, state, ctx.img_cache).ui(ui) {
|
if EditProfileView::new(ctx.i18n, state, ctx.img_cache, ctx.clipboard).ui(ui) {
|
||||||
if let Some(state) = app.view_state.pubkey_to_profile_state.get(kp.pubkey) {
|
if let Some(state) = app.view_state.pubkey_to_profile_state.get(kp.pubkey) {
|
||||||
action = Some(RenderNavAction::ProfileAction(ProfileAction::SaveChanges(
|
action = Some(RenderNavAction::ProfileAction(ProfileAction::SaveChanges(
|
||||||
SaveProfileChanges::new(kp.to_full(), state.clone()),
|
SaveProfileChanges::new(kp.to_full(), state.clone()),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl<'a> AccountLoginView<'a> {
|
|||||||
let text_edit_width = available_width - button_width;
|
let text_edit_width = available_width - button_width;
|
||||||
|
|
||||||
let textedit_resp = ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager, self.i18n));
|
let textedit_resp = ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager, self.i18n));
|
||||||
input_context(&textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear);
|
input_context(ui, &textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear);
|
||||||
|
|
||||||
if eye_button(ui, self.manager.password_visible()).clicked() {
|
if eye_button(ui, self.manager.password_visible()).clicked() {
|
||||||
self.manager.toggle_password_visibility();
|
self.manager.toggle_password_visibility();
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ impl<'a, 'd> PostView<'a, 'd> {
|
|||||||
let out = textedit.show(ui);
|
let out = textedit.show(ui);
|
||||||
|
|
||||||
input_context(
|
input_context(
|
||||||
|
ui,
|
||||||
&out.response,
|
&out.response,
|
||||||
self.note_context.clipboard,
|
self.note_context.clipboard,
|
||||||
&mut self.draft.buffer.text_buffer,
|
&mut self.draft.buffer.text_buffer,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use core::f32;
|
use core::f32;
|
||||||
|
|
||||||
use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit};
|
use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit};
|
||||||
|
use egui_winit::clipboard::Clipboard;
|
||||||
use enostr::ProfileState;
|
use enostr::ProfileState;
|
||||||
use notedeck::{profile::unwrap_profile_url, tr, Images, Localization, NotedeckTextStyle};
|
use notedeck::{profile::unwrap_profile_url, tr, Images, Localization, NotedeckTextStyle};
|
||||||
|
use notedeck_ui::context_menu::{input_context, PasteBehavior};
|
||||||
use notedeck_ui::{profile::banner, ProfilePic};
|
use notedeck_ui::{profile::banner, ProfilePic};
|
||||||
|
|
||||||
pub struct EditProfileView<'a> {
|
pub struct EditProfileView<'a> {
|
||||||
state: &'a mut ProfileState,
|
state: &'a mut ProfileState,
|
||||||
|
clipboard: &'a mut Clipboard,
|
||||||
img_cache: &'a mut Images,
|
img_cache: &'a mut Images,
|
||||||
i18n: &'a mut Localization,
|
i18n: &'a mut Localization,
|
||||||
}
|
}
|
||||||
@@ -16,11 +19,13 @@ impl<'a> EditProfileView<'a> {
|
|||||||
i18n: &'a mut Localization,
|
i18n: &'a mut Localization,
|
||||||
state: &'a mut ProfileState,
|
state: &'a mut ProfileState,
|
||||||
img_cache: &'a mut Images,
|
img_cache: &'a mut Images,
|
||||||
|
clipboard: &'a mut Clipboard,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
i18n,
|
i18n,
|
||||||
state,
|
state,
|
||||||
img_cache,
|
img_cache,
|
||||||
|
clipboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,14 +100,14 @@ impl<'a> EditProfileView<'a> {
|
|||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
));
|
));
|
||||||
ui.add(singleline_textedit(self.state.str_mut("display_name")));
|
singleline_textedit(ui, self.state.str_mut("display_name"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
ui.add(label(
|
ui.add(label(
|
||||||
tr!(self.i18n, "Username", "Profile username field label").as_str(),
|
tr!(self.i18n, "Username", "Profile username field label").as_str(),
|
||||||
));
|
));
|
||||||
ui.add(singleline_textedit(self.state.str_mut("name")));
|
singleline_textedit(ui, self.state.str_mut("name"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
@@ -114,28 +119,28 @@ impl<'a> EditProfileView<'a> {
|
|||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
));
|
));
|
||||||
ui.add(multiline_textedit(self.state.str_mut("picture")));
|
multiline_textedit(ui, self.state.str_mut("picture"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
ui.add(label(
|
ui.add(label(
|
||||||
tr!(self.i18n, "Banner", "Profile banner URL field label").as_str(),
|
tr!(self.i18n, "Banner", "Profile banner URL field label").as_str(),
|
||||||
));
|
));
|
||||||
ui.add(multiline_textedit(self.state.str_mut("banner")));
|
multiline_textedit(ui, self.state.str_mut("banner"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
ui.add(label(
|
ui.add(label(
|
||||||
tr!(self.i18n, "About", "Profile about/bio field label").as_str(),
|
tr!(self.i18n, "About", "Profile about/bio field label").as_str(),
|
||||||
));
|
));
|
||||||
ui.add(multiline_textedit(self.state.str_mut("about")));
|
multiline_textedit(ui, self.state.str_mut("about"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
ui.add(label(
|
ui.add(label(
|
||||||
tr!(self.i18n, "Website", "Profile website field label").as_str(),
|
tr!(self.i18n, "Website", "Profile website field label").as_str(),
|
||||||
));
|
));
|
||||||
ui.add(singleline_textedit(self.state.str_mut("website")));
|
singleline_textedit(ui, self.state.str_mut("website"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
@@ -147,7 +152,7 @@ impl<'a> EditProfileView<'a> {
|
|||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
));
|
));
|
||||||
ui.add(multiline_textedit(self.state.str_mut("lud16")));
|
multiline_textedit(ui, self.state.str_mut("lud16"), self.clipboard);
|
||||||
});
|
});
|
||||||
|
|
||||||
in_frame(ui, |ui| {
|
in_frame(ui, |ui| {
|
||||||
@@ -159,7 +164,8 @@ impl<'a> EditProfileView<'a> {
|
|||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
));
|
));
|
||||||
ui.add(singleline_textedit(self.state.str_mut("nip05")));
|
|
||||||
|
singleline_textedit(ui, self.state.str_mut("nip05"), self.clipboard);
|
||||||
|
|
||||||
let Some(nip05) = self.state.nip05() else {
|
let Some(nip05) = self.state.nip05() else {
|
||||||
return;
|
return;
|
||||||
@@ -208,21 +214,29 @@ fn label(text: &str) -> impl egui::Widget + '_ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn singleline_textedit(data: &mut String) -> impl egui::Widget + '_ {
|
fn singleline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) {
|
||||||
|
let r = ui.add(
|
||||||
TextEdit::singleline(data)
|
TextEdit::singleline(data)
|
||||||
.min_size(vec2(0.0, 40.0))
|
.min_size(vec2(0.0, 40.0))
|
||||||
.vertical_align(egui::Align::Center)
|
.vertical_align(egui::Align::Center)
|
||||||
.margin(Margin::symmetric(12, 10))
|
.margin(Margin::symmetric(12, 10))
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY),
|
||||||
|
);
|
||||||
|
|
||||||
|
input_context(ui, &r, clipboard, data, PasteBehavior::Clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn multiline_textedit(data: &mut String) -> impl egui::Widget + '_ {
|
fn multiline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) {
|
||||||
|
let r = ui.add(
|
||||||
TextEdit::multiline(data)
|
TextEdit::multiline(data)
|
||||||
// .min_size(vec2(0.0, 40.0))
|
// .min_size(vec2(0.0, 40.0))
|
||||||
.vertical_align(egui::Align::TOP)
|
.vertical_align(egui::Align::TOP)
|
||||||
.margin(Margin::symmetric(12, 10))
|
.margin(Margin::symmetric(12, 10))
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(1)
|
.desired_rows(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
input_context(ui, &r, clipboard, data, PasteBehavior::Clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_frame(ui: &mut egui::Ui, contents: impl FnOnce(&mut egui::Ui)) {
|
fn in_frame(ui: &mut egui::Ui, contents: impl FnOnce(&mut egui::Ui)) {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ fn search_box(
|
|||||||
.frame(false),
|
.frame(false),
|
||||||
);
|
);
|
||||||
|
|
||||||
input_context(&response, clipboard, input, PasteBehavior::Append);
|
input_context(ui, &response, clipboard, input, PasteBehavior::Append);
|
||||||
|
|
||||||
let mut requested_focus = false;
|
let mut requested_focus = false;
|
||||||
if focus_state == FocusState::ShouldRequestFocus {
|
if focus_state == FocusState::ShouldRequestFocus {
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
"Label for appearance settings section",
|
"Label for appearance settings section",
|
||||||
);
|
);
|
||||||
settings_group(ui, title, |ui| {
|
settings_group(ui, title, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(richtext_small(tr!(
|
ui.label(richtext_small(tr!(
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
"Font size:",
|
"Font size:",
|
||||||
@@ -207,7 +207,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
|
|
||||||
let current_zoom = ui.ctx().zoom_factor();
|
let current_zoom = ui.ctx().zoom_factor();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(richtext_small(tr!(
|
ui.label(richtext_small(tr!(
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
"Zoom Level:",
|
"Zoom Level:",
|
||||||
@@ -260,7 +260,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(richtext_small(tr!(
|
ui.label(richtext_small(tr!(
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
"Language:",
|
"Language:",
|
||||||
@@ -288,7 +288,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(richtext_small(tr!(
|
ui.label(richtext_small(tr!(
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
"Theme:",
|
"Theme:",
|
||||||
@@ -441,7 +441,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
"Label for others settings section"
|
"Label for others settings section"
|
||||||
);
|
);
|
||||||
settings_group(ui, title, |ui| {
|
settings_group(ui, title, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(richtext_small(tr!(
|
ui.label(richtext_small(tr!(
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
"Sort replies newest first:",
|
"Sort replies newest first:",
|
||||||
|
|||||||
@@ -237,8 +237,10 @@ fn show_no_wallet(
|
|||||||
.password(true);
|
.password(true);
|
||||||
|
|
||||||
// add paste context menu
|
// add paste context menu
|
||||||
|
let text_edit_resp = ui.add(text_edit);
|
||||||
input_context(
|
input_context(
|
||||||
&ui.add(text_edit),
|
ui,
|
||||||
|
&text_edit_resp,
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut state.buf,
|
&mut state.buf,
|
||||||
PasteBehavior::Clear,
|
PasteBehavior::Clear,
|
||||||
@@ -388,13 +390,17 @@ fn show_default_zap(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let id = ui.id().with("default_zap_amount");
|
let id = ui.id().with("default_zap_amount");
|
||||||
ui.add(
|
|
||||||
|
{
|
||||||
|
let r = ui.add(
|
||||||
egui::TextEdit::singleline(text)
|
egui::TextEdit::singleline(text)
|
||||||
.desired_width(desired_width)
|
.desired_width(desired_width)
|
||||||
.margin(egui::Margin::same(8))
|
.margin(egui::Margin::same(8))
|
||||||
.font(font)
|
.font(font)
|
||||||
.id(id),
|
.id(id));
|
||||||
);
|
|
||||||
|
notedeck_ui::include_input(ui, &r);
|
||||||
|
}
|
||||||
|
|
||||||
ui.memory_mut(|m| m.request_focus(id));
|
ui.memory_mut(|m| m.request_focus(id));
|
||||||
|
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ impl<'a> DaveUi<'a> {
|
|||||||
)
|
)
|
||||||
.frame(false),
|
.frame(false),
|
||||||
);
|
);
|
||||||
|
notedeck_ui::include_input(ui, &r);
|
||||||
|
|
||||||
if r.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
if r.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||||
DaveResponse::send()
|
DaveResponse::send()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ fn handle_paste(clipboard: &mut Clipboard, input: &mut String, paste_behavior: P
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_context(
|
pub fn input_context(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
response: &egui::Response,
|
response: &egui::Response,
|
||||||
clipboard: &mut Clipboard,
|
clipboard: &mut Clipboard,
|
||||||
input: &mut String,
|
input: &mut String,
|
||||||
@@ -46,4 +47,7 @@ pub fn input_context(
|
|||||||
if response.middle_clicked() {
|
if response.middle_clicked() {
|
||||||
handle_paste(clipboard, input, paste_behavior)
|
handle_paste(clipboard, input, paste_behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for keyboard visibility
|
||||||
|
crate::include_input(ui, response)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,3 +62,34 @@ pub fn secondary_label(ui: &mut egui::Ui, s: impl Into<String>) -> egui::Respons
|
|||||||
let color = ui.style().visuals.noninteractive().fg_stroke.color;
|
let color = ui.style().visuals.noninteractive().fg_stroke.color;
|
||||||
ui.add(Label::new(RichText::new(s).size(10.0).color(color)).selectable(false))
|
ui.add(Label::new(RichText::new(s).size(10.0).color(color)).selectable(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INPUT_RECT_KEY: &str = "notedeck_input_rect";
|
||||||
|
|
||||||
|
/// Includes an input rect for keyboard visibility purposes. We use this to move the screen up if
|
||||||
|
/// a soft keyboard intersects with the input box
|
||||||
|
pub fn include_input(ui: &mut egui::Ui, resp: &egui::Response) {
|
||||||
|
// only include input if we have focus
|
||||||
|
if !resp.has_focus() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.data_mut(|d| {
|
||||||
|
let id = egui::Id::new(INPUT_RECT_KEY);
|
||||||
|
match d.get_temp::<egui::Rect>(id) {
|
||||||
|
Some(r) => d.insert_temp(id, resp.rect.union(r)),
|
||||||
|
None => d.insert_temp(id, resp.rect),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the last input rect for keyboard visibility purposes. We use this to move the screen up if
|
||||||
|
/// a soft keyboard intersects with the input box
|
||||||
|
pub fn input_rect(ui: &mut egui::Ui) -> Option<egui::Rect> {
|
||||||
|
ui.data(|d| d.get_temp(egui::Id::new(INPUT_RECT_KEY)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the last input rect for keyboard visibility purposes. We use this to move the screen up if
|
||||||
|
/// a soft keyboard intersects with the input box
|
||||||
|
pub fn clear_input_rect(ui: &mut egui::Ui) {
|
||||||
|
ui.data_mut(|d| d.remove::<egui::Rect>(egui::Id::new(INPUT_RECT_KEY)))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user