feat(android): follow the activity lifecycle and build an APK (Issue #42)

- The drawing surface is created once the native window exists (Resumed)
  and released when the app goes to the background (Suspended); before
  that everything is saved, as Android may stop the app at any time
- Android's back button acts like Escape instead of closing the app
- Panics are written to logcat; no update check on Android yet
- scripts/package-android.sh builds target/ink-notes_<version>_arm64.apk
  without Gradle (NDK, aapt2, zipalign, apksigner); signing key from
  ANDROID_KEYSTORE/ANDROID_KEYSTORE_PASS/ANDROID_KEY_ALIAS, otherwise
  the debug key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
haraldandClaude Opus 5.5 committed 2026-10-27 18:23:28 +01:00
1 parent ce3a1f697b
commit 7faf78ddeb
9 files changed
+1662 -353

No files matched your search

+45
View File
@@ -82,6 +82,51 @@ pub fn desktop_event_loop() -> Result<winit::event_loop::EventLoop<()>, winit::e
builder.build()
}
/// Waits until the window can be drawn to. On Android the native window only exists after
/// `Event::Resumed` (Issue #42); on the desktop it is there right away.
pub fn wait_until_resumed(
#[allow(unused_variables)] event_loop: &mut winit::event_loop::EventLoop<()>,
) {
#[cfg(target_os = "android")]
{
use winit::event::Event;
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
let mut resumed = false;
while !resumed {
let status = event_loop.pump_events(None, |event, _| {
if matches!(event, Event::Resumed) {
resumed = true;
}
});
if let PumpStatus::Exit(_) = status {
std::process::exit(0);
}
}
}
}
/// Runs the event loop until the app quits. On Android the loop was already started by
/// [`wait_until_resumed`], so it is driven with `pump_events` (`run` would refuse to start).
pub fn run_event_loop<F>(
#[allow(unused_mut)] mut event_loop: winit::event_loop::EventLoop<()>,
#[allow(unused_mut)] mut handler: F,
) -> Result<(), winit::error::EventLoopError>
where
F: FnMut(winit::event::Event<()>, &winit::event_loop::EventLoopWindowTarget<()>),
{
#[cfg(target_os = "android")]
{
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
loop {
if let PumpStatus::Exit(_) = event_loop.pump_events(None, &mut handler) {
return Ok(());
}
}
}
#[cfg(not(target_os = "android"))]
event_loop.run(handler)
}
/// Gives the window the app id (Wayland: app_id, X11: WM_CLASS), so that dock and task bar
/// show the app icon for it.
pub fn with_app_id(