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:
@@ -57,6 +57,7 @@ tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = { workspace = true }
|
||||
android-activity = { workspace = true }
|
||||
|
||||
[features]
|
||||
puffin = ["puffin_egui", "dep:puffin"]
|
||||
|
||||
@@ -22,6 +22,9 @@ use std::rc::Rc;
|
||||
use tracing::{error, info};
|
||||
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android_activity::AndroidApp;
|
||||
|
||||
pub enum AppAction {
|
||||
Note(NoteAction),
|
||||
ToggleChrome,
|
||||
@@ -51,6 +54,9 @@ pub struct Notedeck {
|
||||
frame_history: FrameHistory,
|
||||
job_pool: JobPool,
|
||||
i18n: Localization,
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
android_app: Option<AndroidApp>,
|
||||
}
|
||||
|
||||
/// Our chrome, which is basically nothing
|
||||
@@ -138,6 +144,11 @@ fn setup_puffin() {
|
||||
}
|
||||
|
||||
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 {
|
||||
#[cfg(feature = "puffin")]
|
||||
setup_puffin();
|
||||
@@ -272,6 +283,8 @@ impl Notedeck {
|
||||
zaps,
|
||||
job_pool,
|
||||
i18n,
|
||||
#[cfg(target_os = "android")]
|
||||
android_app: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +348,8 @@ impl Notedeck {
|
||||
frame_history: &mut self.frame_history,
|
||||
job_pool: &mut self.job_pool,
|
||||
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 nostrdb::Ndb;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android_activity::AndroidApp;
|
||||
use egui::{Pos2, Rect};
|
||||
// TODO: make this interface more sandboxed
|
||||
|
||||
pub struct AppContext<'a> {
|
||||
@@ -26,4 +29,49 @@ pub struct AppContext<'a> {
|
||||
pub frame_history: &'a mut FrameHistory,
|
||||
pub job_pool: &'a mut JobPool,
|
||||
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 app::{App, AppAction, Notedeck};
|
||||
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 filter::{FilterState, FilterStates, UnifiedSubscription};
|
||||
pub use fonts::NamedFontFamily;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub extern "C" fn Java_com_damus_notedeck_KeyboardHeightHelper_nativeKeyboardHei
|
||||
debug!("updating virtual keyboard height {}", height);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -20,3 +20,13 @@ pub fn virtual_keyboard_height(virt: bool) -> i32 {
|
||||
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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user