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:
William Casarin
2025-08-07 11:25:41 -07:00
parent 3aa4d00053
commit 77ac91e810
24 changed files with 276 additions and 94 deletions

View File

@@ -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(
"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(
@@ -57,7 +57,7 @@ pub async fn android_main(app: AndroidApp) {
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(
"Damus Notedeck",
@@ -65,6 +65,7 @@ pub async fn android_main(app: AndroidApp) {
Box::new(move |cc| {
let ctx = &cc.egui_ctx;
let mut notedeck = Notedeck::new(ctx, path, &app_args);
notedeck.set_android_context(app.clone());
notedeck.setup(ctx);
let chrome = Chrome::new_with_apps(cc, &app_args, &mut notedeck)?;
notedeck.set_app(chrome);

View File

@@ -8,6 +8,7 @@ use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference
use egui_extras::{Size, StripBuilder};
use nostrdb::{ProfileRecord, Transaction};
use notedeck::Error;
use notedeck::SoftKeyboardContext;
use notedeck::{
tr, App, AppAction, AppContext, Localization, Notedeck, NotedeckOptions, NotedeckTextStyle,
UserAccount, WalletType,
@@ -267,9 +268,40 @@ impl Chrome {
let amt_open = self.amount_open(ui);
let r = self.panel(ctx, StripBuilder::new(ui), amt_open);
// virtual keyboard
if self.options.contains(ChromeOptions::VirtualKeyboard) {
virtual_keyboard_ui(ui);
let skb_ctx = if self.options.contains(ChromeOptions::VirtualKeyboard) {
SoftKeyboardContext::Virtual
} 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
@@ -912,12 +944,7 @@ fn repaint_causes_window(ui: &mut egui::Ui, causes: &HashMap<egui::RepaintCause,
});
}
fn virtual_keyboard_ui(ui: &mut egui::Ui) {
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);
fn virtual_keyboard_ui(ui: &mut egui::Ui, rect: egui::Rect) {
let painter = ui.painter_at(rect);
painter.rect_filled(rect, 0.0, Color32::from_black_alpha(200));