android: migrate back to gameactivity, update to API 36, fix back navigation at network panel

This commit is contained in:
ardocrat
2025-11-21 01:34:51 +03:00
parent 646a7c5e04
commit ed2dc880aa
17 changed files with 370 additions and 193 deletions
+31 -4
View File
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::atomic::{AtomicBool, Ordering};
use egui::epaint::RectShape;
use egui::{Align, Context, CornerRadius, CursorIcon, LayerId, Layout, Modifiers, Order, ResizeDirection, Stroke, StrokeKind, UiBuilder, ViewportCommand};
use lazy_static::lazy_static;
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
use crate::gui::platform::PlatformCallbacks;
@@ -22,6 +24,11 @@ use crate::gui::views::{Content, KeyboardContent, Modal, TitlePanel, View};
use crate::gui::Colors;
use crate::AppConfig;
lazy_static! {
/// State to check if platform Back button was pressed.
static ref BACK_BUTTON_PRESSED: AtomicBool = AtomicBool::new(false);
}
/// Implements ui entry point and contains platform-specific callbacks.
pub struct App<Platform> {
/// Handles platform-specific functionality.
@@ -63,10 +70,15 @@ impl<Platform: PlatformCallbacks> App<Platform> {
self.first_draw = false;
}
// Handle Esc keyboard key event.
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape) ||
// Handle Esc keyboard key event and platform Back button key event.
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape) ||
i.consume_key(Modifiers::NONE, egui::Key::BrowserBack)) {
self.content.on_back(&self.platform);
// Pass event to content.
self.content.on_back(ctx, &self.platform);
if back_pressed {
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
}
// Request repaint to update previous content.
ctx.request_repaint();
}
@@ -134,7 +146,9 @@ impl<Platform: PlatformCallbacks> App<Platform> {
false
};
if keyboard_showing {
ctx.move_to_top(LayerId::new(Order::Middle, egui::Id::new(KeyboardContent::WINDOW_ID)));
ctx.move_to_top(
LayerId::new(Order::Middle, egui::Id::new(KeyboardContent::WINDOW_ID))
);
}
}
// Reset keyboard state for newly opened modal.
@@ -397,4 +411,17 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
}
Colors::TRANSPARENT.to_normalized_gamma_f32()
}
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Handle Back key code event from Android.
pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
BACK_BUTTON_PRESSED.store(true, Ordering::Relaxed);
}
+8 -2
View File
@@ -146,9 +146,15 @@ impl Content {
pub const EXIT_CONFIRMATION_MODAL: &'static str = "exit_confirmation_modal";
/// Called to navigate back, return `true` if action was not consumed.
pub fn on_back(&mut self, cb: &dyn PlatformCallbacks) -> bool {
pub fn on_back(&mut self, ctx: &egui::Context, cb: &dyn PlatformCallbacks) -> bool {
if Modal::on_back() {
if self.wallets.on_back(cb) {
let dual_panel = Self::is_dual_panel_mode(ctx);
if !dual_panel && Self::is_network_panel_open() {
if self.network.on_back() {
Self::toggle_network_panel();
return false;
}
} else if self.wallets.on_back(cb) {
Self::show_exit_modal();
return false;
}
+89 -11
View File
@@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Layout, TextBuffer, TextStyle, Widget, Align};
use std::sync::Arc;
use egui::{Layout, TextBuffer, TextStyle, Widget, Align, ViewportCommand};
use egui::text_edit::TextEditState;
use lazy_static::lazy_static;
use parking_lot::RwLock;
use crate::gui::Colors;
use crate::gui::icons::{CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN};
use crate::gui::platform::PlatformCallbacks;
@@ -24,11 +28,11 @@ use crate::gui::views::{KeyboardEvent, View};
pub struct TextEdit {
/// View identifier.
id: egui::Id,
/// Check if horizontal centering is needed.
/// Horizontal text centering is needed.
h_center: bool,
/// Check if focus is needed.
/// Focus is needed.
focus: bool,
/// Check if focus request was passed.
/// Focus request was passed.
focus_request: bool,
/// Hide letters and draw button to show/hide letters.
password: bool,
@@ -38,9 +42,9 @@ pub struct TextEdit {
paste: bool,
/// Show button to scan QR code into text.
scan_qr: bool,
/// Callback when scan button was pressed.
/// Scan button was pressed.
pub scan_pressed: bool,
/// Callback when Enter key was pressed.
/// Tab or Enter keys were pressed to focus on next line.
pub enter_pressed: bool,
/// Flag to enter only numbers.
numeric: bool,
@@ -65,7 +69,7 @@ impl TextEdit {
scan_pressed: false,
enter_pressed: false,
numeric: false,
no_soft_keyboard: false,
no_soft_keyboard: is_android(),
}
}
@@ -151,6 +155,7 @@ impl TextEdit {
// Reset keyboard state for newly focused.
if clicked || self.focus_request {
ui.ctx().send_viewport_cmd(ViewportCommand::IMEAllowed(true));
KeyboardContent::reset_window_state();
}
@@ -160,9 +165,10 @@ impl TextEdit {
data.insert_temp(focused_input_id, self.id);
});
self.enter_pressed = self.on_soft_input(ui, self.id, false, input);
// Check Enter key input.
// Check Enter or Tab keys press.
if !self.focus_request {
if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter)) {
if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter) ||
i.key_pressed(egui::Key::Tab)) {
self.enter_pressed = true;
}
}
@@ -175,12 +181,24 @@ impl TextEdit {
}
});
});
// Repaint on Android to handle input from Java code without delays.
if is_android() {
ui.ctx().request_repaint();
}
}
/// Apply soft keyboard input data to provided String, returns `true` if Enter was pressed.
fn on_soft_input(&self, ui: &mut egui::Ui, id: egui::Id, multiline: bool, value: &mut String)
-> bool {
if let Some(input) = KeyboardContent::consume_event() {
let event: Option<KeyboardEvent> = if is_android() {
let mut w_input = LAST_SOFT_KEYBOARD_EVENT.write();
w_input.take()
} else {
KeyboardContent::consume_event()
};
// Handle keyboard input event.
if let Some(e) = event {
let mut enter_pressed = false;
let mut state = TextEditState::load(ui.ctx(), id).unwrap();
match state.cursor.char_range() {
@@ -194,7 +212,7 @@ impl TextEdit {
r.secondary.index as f32) as usize;
let end_select = f32::max(r.primary.index as f32,
r.secondary.index as f32) as usize;
match input {
match e {
KeyboardEvent::TEXT(text) => {
if selected {
*value = {
@@ -318,4 +336,64 @@ impl TextEdit {
self.no_soft_keyboard = true;
self
}
}
/// Check if current system is Android.
fn is_android() -> bool {
egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Android
}
lazy_static! {
static ref LAST_SOFT_KEYBOARD_EVENT: Arc<RwLock<Option<KeyboardEvent>>> = Arc::new(RwLock::new(None));
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Callback from Java code with last entered character from soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onTextInput(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
char: jni::sys::jstring
) {
use jni::objects::JString;
unsafe {
let j_obj = JString::from_raw(char);
let j_str = _env.get_string_unchecked(j_obj.as_ref()).unwrap();
match j_str.to_str() {
Ok(str) => {
let mut w_input = LAST_SOFT_KEYBOARD_EVENT.write();
*w_input = Some(KeyboardEvent::TEXT(str.to_string()));
}
Err(_) => {}
}
}
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Callback from Java code when Clear key was pressed at soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onClearInput(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
) {
let mut w_input = LAST_SOFT_KEYBOARD_EVENT.write();
*w_input = Some(KeyboardEvent::CLEAR);
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Callback from Java code when Enter key was pressed at soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onEnterInput(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
) {
let mut w_input = LAST_SOFT_KEYBOARD_EVENT.write();
*w_input = Some(KeyboardEvent::ENTER);
}
+10
View File
@@ -213,6 +213,16 @@ impl NetworkContent {
}
}
/// Navigate back, return `true` if action was not consumed.
pub fn on_back(&mut self) -> bool {
if self.showing_settings() {
// Close settings.
self.settings_content = None;
return false;
}
true
}
/// Check if application settings content is showing.
pub fn showing_settings(&self) -> bool {
self.settings_content.is_some()
+8 -5
View File
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, CornerRadius, Id, Layout, Margin, RichText, ScrollArea, StrokeKind};
use std::time::Duration;
use egui::os::OperatingSystem;
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::settings::SettingsContent;
@@ -130,6 +130,12 @@ impl ContentContainer for WalletsContent {
}
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
ui.ctx().request_repaint_after(if OperatingSystem::from_target_os() == OperatingSystem::Android {
Duration::from_millis(100)
} else {
Duration::from_millis(1000)
});
if let Some(data) = crate::consume_incoming_data() {
if !data.is_empty() {
self.on_data(ui, Some(data), cb);
@@ -221,9 +227,6 @@ impl ContentContainer for WalletsContent {
..Default::default()
})
.show_animated_inside(ui, !list_hidden, |ui| {
if !dual_panel && !showing_wallet {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
}
// Show wallet list.
self.wallet_list_ui(ui, cb);
});
-7
View File
@@ -15,7 +15,6 @@
use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, Margin, RichText, ScrollArea};
use grin_chain::SyncStatus;
use std::time::Duration;
use crate::gui::icons::{ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, GEAR_FINE, POWER, STACK};
use crate::gui::platform::PlatformCallbacks;
@@ -83,12 +82,6 @@ impl WalletContentContainer for WalletContent {
}
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
if self.account_content.can_back() {
ui.ctx().request_repaint();
} else {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
}
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
let show_wallets_dual = AppConfig::show_wallets_at_dual_panel();
+1 -1
View File
@@ -52,7 +52,7 @@ fn android_main(app: AndroidApp) {
{
std::env::set_var("RUST_BACKTRACE", "full");
let log_config = android_logger::Config::default()
.with_max_level(log::LevelFilter::Info)
.with_max_level(log::LevelFilter::Debug)
.with_tag("grim");
android_logger::init_once(log_config);
}