Files
notedeck/crates/notedeck_chrome/src/android.rs
William Casarin 77ac91e810 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
2025-08-19 11:29:45 -07:00

153 lines
4.4 KiB
Rust

//#[cfg(target_os = "android")]
//use egui_android::run_android;
use egui_winit::winit::platform::android::activity::AndroidApp;
use crate::chrome::Chrome;
use notedeck::Notedeck;
#[no_mangle]
#[tokio::main]
pub async fn android_main(app: AndroidApp) {
//use tracing_logcat::{LogcatMakeWriter, LogcatTag};
use tracing_subscriber::{prelude::*, EnvFilter};
std::env::set_var("RUST_BACKTRACE", "full");
//std::env::set_var("DAVE_ENDPOINT", "http://ollama.jb55.com/v1");
//std::env::set_var("DAVE_MODEL", "hhao/qwen2.5-coder-tools:latest");
std::env::set_var(
"RUST_LOG",
"egui=debug,egui-winit=debug,winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug",
);
//std::env::set_var(
// "RUST_LOG",
// "enostr=debug,notedeck_columns=debug,notedeck_chrome=debug",
//);
//let writer =
//LogcatMakeWriter::new(LogcatTag::Target).expect("Failed to initialize logcat writer");
let fmt_layer = tracing_subscriber::fmt::layer()
.with_level(false)
.with_target(false)
.without_time();
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init();
let path = app.internal_data_path().expect("data path");
let mut options = eframe::NativeOptions {
depth_buffer: 24,
..eframe::NativeOptions::default()
};
options.renderer = eframe::Renderer::Wgpu;
// Clone `app` to use it both in the closure and later in the function
//let app_clone_for_event_loop = app.clone();
//options.event_loop_builder = Some(Box::new(move |builder| {
// builder.with_android_app(app_clone_for_event_loop);
//}));
options.android_app = Some(app.clone());
let app_args = get_app_args(app.clone());
let _res = eframe::run_native(
"Damus Notedeck",
options,
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);
Ok(Box::new(notedeck))
}),
);
}
/*
Read args from a config file:
- allows use of more interesting args w/o risk of checking them in by mistake
- allows use of different args w/o rebuilding the app
- uses compiled in defaults if config file missing or broken
Example android-config.json:
```
{
"args": [
"argv0-placeholder",
"--npub",
"npub1h50pnxqw9jg7dhr906fvy4mze2yzawf895jhnc3p7qmljdugm6gsrurqev",
"-c",
"contacts",
"-c",
"notifications"
]
}
```
Install/update android-config.json with:
```
adb push android-config.json /sdcard/Android/data/com.damus.notedeck/files/android-config.json
```
Using internal storage would be better but it seems hard to get the config file onto
the device ...
*/
fn get_app_args(_app: AndroidApp) -> Vec<String> {
vec!["argv0-placeholder".to_string()]
/*
use serde_json::value;
use std::fs;
use std::path::PathBuf;
let external_data_path: pathbuf = app
.external_data_path()
.expect("external data path")
.to_path_buf();
let config_file = external_data_path.join("android-config.json");
let initial_user = hex::encode(notedeck::FALLBACK_PUBKEY().bytes());
let default_args = vec![
"argv0-placeholder",
"--no-tmp-columns",
"--pub",
&initial_user,
"-c",
"contacts",
"-c",
"notifications",
]
.into_iter()
.map(|s| s.to_string())
.collect();
if config_file.exists() {
if let Ok(config_contents) = fs::read_to_string(config_file) {
if let Ok(json) = serde_json::from_str::<Value>(&config_contents) {
if let Some(args_array) = json.get("args").and_then(|v| v.as_array()) {
let config_args = args_array
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
return config_args;
}
}
}
}
default_args // Return the default args if config is missing or invalid
*/
}