extern crate alloc;
extern crate std;
use alloc::boxed::Box;
use jni::objects::{Global, JClass, JObject, JValue};
use jni::sys::{jboolean, jint, jlong, jobject};
use jni::{Env, EnvUnowned, jni_sig, jni_str};
use std::sync::Arc;
struct JniNavigationCallbackData {
jvm: Arc<jni::JavaVM>,
callback: Global<JObject<'static>>,
}
impl JniNavigationCallbackData {
fn with_env<R>(&self, f: impl FnOnce(&mut Env) -> R) -> R {
super::with_attached_env(&self.jvm, f)
.expect("navigation callback failed to attach its JVM thread")
}
}
unsafe extern "C" fn jni_navigation_apply(
data: *mut (),
transaction: crate::components::navigation::WuiNavigationTransaction,
) {
let data = unsafe { &*(data as *const JniNavigationCallbackData) };
data.with_env(|env| {
let view_class = env
.find_class(jni_str!("dev/waterui/android/runtime/NavigationViewStruct"))
.expect("NavigationViewStruct class not found");
let inserted = env
.new_object_array(
super::array_len(transaction.inserted.len()),
view_class,
JObject::null(),
)
.expect("failed to allocate navigation transaction destination array");
for (index, view) in transaction.inserted.iter().enumerate() {
let view = crate::jni::convert::struct_to_java(env, view);
inserted
.set_element(env, index, view)
.expect("failed to insert navigation transaction destination");
}
env.call_method(
data.callback.as_obj(),
jni_str!("onApply"),
jni_sig!("(JII[Ldev/waterui/android/runtime/NavigationViewStruct;)V"),
&[
JValue::Long(transaction.id as jlong),
JValue::Int(
i32::try_from(transaction.retained_prefix)
.expect("navigation retained prefix must fit Java Int"),
),
JValue::Int(
i32::try_from(transaction.removed)
.expect("navigation removal count must fit Java Int"),
),
JValue::Object(&inserted),
],
)
.expect("jni_navigation_apply: failed to call callback.onApply(...)");
transaction.inserted.consume();
});
}
unsafe extern "C" fn jni_navigation_drop(data: *mut ()) {
let _ = unsafe { Box::from_raw(data as *mut JniNavigationCallbackData) };
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallNavigationController<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
callback: JObject<'local>,
) {
super::with_env(&mut env, |env| {
let global_callback = env
.new_global_ref(callback)
.expect("Failed to create global ref for navigation callback");
let data = Box::new(JniNavigationCallbackData {
jvm: Arc::new(
env.get_java_vm()
.expect("navigation controller failed to access JavaVM"),
),
callback: global_callback,
});
let data_ptr = Box::into_raw(data) as *mut ();
unsafe {
crate::components::navigation::waterui_env_install_navigation_controller(
env_ptr as *mut crate::WuiEnv,
data_ptr,
jni_navigation_apply,
jni_navigation_drop,
)
}
});
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envHasNavigationController<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
) -> jboolean {
let wui_env = env_ptr as *const crate::WuiEnv;
unsafe { crate::components::navigation::waterui_env_has_navigation_controller(wui_env) }
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_navigationRequestPop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
count: jint,
) {
let count = usize::try_from(count).expect("Android navigation pop count must be positive");
assert!(count != 0, "Android navigation pop count must be non-zero");
let env = unsafe { crate::borrow_ffi(env_ptr as *const crate::WuiEnv) };
let controller = env
.get::<waterui_navigation::NavigationController>()
.expect("Android requested navigation pop without an installed controller");
controller.request_pop(count);
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_navigationCompleteNativePop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
count: jint,
) {
let count = usize::try_from(count).expect("Android navigation pop count must be positive");
assert!(count != 0, "Android navigation pop count must be non-zero");
let env = unsafe { crate::borrow_ffi(env_ptr as *const crate::WuiEnv) };
let controller = env
.get::<waterui_navigation::NavigationController>()
.expect("Android completed navigation pop without an installed controller");
controller.complete_native_pop(count);
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_navigationTransitionCompleted<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
id: jlong,
) -> jboolean {
let id = u64::try_from(id).expect("Android navigation transaction id must be positive");
let env = unsafe { crate::borrow_ffi(env_ptr as *const crate::WuiEnv) };
let controller = env
.get::<waterui_navigation::NavigationController>()
.expect("Android completed navigation transaction without an installed controller");
controller.transition_completed(id)
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_navigationTransitionCancelled<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
id: jlong,
) -> jboolean {
let id = u64::try_from(id).expect("Android navigation transaction id must be positive");
let env = unsafe { crate::borrow_ffi(env_ptr as *const crate::WuiEnv) };
let controller = env
.get::<waterui_navigation::NavigationController>()
.expect("Android cancelled navigation transaction without an installed controller");
controller.transition_cancelled(id)
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_tabContent<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
content_ptr: jlong,
env_ptr: jlong,
) -> jobject {
use crate::components::navigation::WuiTabContent;
let content = content_ptr as *mut WuiTabContent;
let wui_env = env_ptr as *const crate::WuiEnv;
let nav_view = unsafe { crate::components::navigation::waterui_tab_content(content, wui_env) };
super::with_env(&mut env, |env| {
crate::jni::convert::struct_to_java(env, &nav_view).into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_splitNavigationDetailContent<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
detail_ptr: jlong,
selected_id: jint,
env_ptr: jlong,
) -> jobject {
use crate::components::navigation::WuiNavigationSplitDetail;
let detail = detail_ptr as *mut WuiNavigationSplitDetail;
let wui_env = env_ptr as *const crate::WuiEnv;
let nav_view = unsafe {
crate::components::navigation::waterui_split_navigation_detail_content(
detail,
crate::id::WuiId { inner: selected_id },
wui_env,
)
};
super::with_env(&mut env, |env| {
crate::jni::convert::struct_to_java(env, &nav_view).into_raw()
})
}