use jni::{
Env,
objects::{JObject, JString},
refs::{LoaderContext, Reference},
};
use jni_min_helper::{DynamicProxy, JInteger};
use winio_handle::AsWindow;
use winio_primitive::{MessageBoxButton, MessageBoxResponse, MessageBoxStyle};
use crate::{
Error, Result,
java::{
android::{
app::AlertDialogBuilder,
content::{
DialogInterfaceOnCancelListener, DialogInterfaceOnClickListener,
DialogInterfaceOnDismissListener,
},
},
custom::Activity,
},
vm_exec,
};
#[derive(Debug, Default, Clone)]
pub struct MessageBox {
msg: String,
title: String,
instr: String,
style: MessageBoxStyle,
btns: MessageBoxButton,
cbtns: Vec<CustomButton>,
}
impl MessageBox {
pub fn new() -> Self {
Self {
msg: String::new(),
title: String::new(),
instr: String::new(),
style: MessageBoxStyle::None,
btns: MessageBoxButton::empty(),
cbtns: vec![],
}
}
pub fn message(&mut self, msg: &str) {
self.msg = msg.to_string();
}
pub fn title(&mut self, title: &str) {
self.title = title.to_string();
}
pub fn instruction(&mut self, instr: &str) {
self.instr = instr.to_string();
}
pub fn style(&mut self, style: MessageBoxStyle) {
self.style = style;
}
pub fn buttons(&mut self, btns: MessageBoxButton) {
self.btns = btns;
}
pub fn custom_button(&mut self, btn: CustomButton) {
self.cbtns.push(btn);
}
pub fn custom_buttons(&mut self, btn: impl IntoIterator<Item = CustomButton>) {
self.cbtns.extend(btn);
}
fn texts_and_responses<'local>(
env: &mut Env<'local>,
btns: MessageBoxButton,
cbtns: &[CustomButton],
) -> Result<([Option<MessageBoxResponse>; 3], [JString<'local>; 3])> {
let mut responses = [None; 3];
let mut texts = [JString::null(), JString::null(), JString::null()];
const POSITIVE: usize = 0;
const NEGATIVE: usize = 1;
const NEUTRAL: usize = 2;
let mut push = |response: MessageBoxResponse, text: &str, index: usize| {
if responses[index].replace(response).is_some() {
return Err(Error::NotSupported);
}
texts[index] = env.new_string(text)?;
Ok(())
};
if btns.contains(MessageBoxButton::Ok) {
push(MessageBoxResponse::Ok, "Ok", POSITIVE)?;
}
if btns.contains(MessageBoxButton::Cancel) {
push(MessageBoxResponse::Cancel, "Cancel", NEGATIVE)?;
}
if btns.contains(MessageBoxButton::Yes) {
push(MessageBoxResponse::Yes, "Yes", POSITIVE)?;
}
if btns.contains(MessageBoxButton::No) {
push(MessageBoxResponse::No, "No", NEGATIVE)?;
}
if btns.contains(MessageBoxButton::Retry) {
push(MessageBoxResponse::Retry, "Retry", POSITIVE)?;
}
if btns.contains(MessageBoxButton::Close) {
push(MessageBoxResponse::Close, "Close", NEGATIVE)?;
}
for cbtn in cbtns {
push(
MessageBoxResponse::Custom(cbtn.result),
&cbtn.label,
NEUTRAL,
)?;
}
Ok((responses, texts))
}
pub fn show(
self,
parent: Option<impl AsWindow>,
) -> Result<impl Future<Output = Result<MessageBoxResponse>> + 'static> {
let (mut rx, _proxy) = vm_exec(|env| {
let activity = if let Some(parent) = parent {
let act = env.new_local_ref(parent.as_window().to_android())?;
unsafe { Activity::from_raw(env, act.into_raw()) }
} else {
crate::current_activity(env)?
};
let builder = AlertDialogBuilder::new(env, &activity)?;
let msg = if self.instr.is_empty() {
env.new_string(self.msg)?
} else {
env.new_string(format!("{}\n\n{}", self.instr, self.msg))?
};
builder.set_message(env, &msg)?;
if !self.title.is_empty() {
let title = env.new_string(self.title)?;
builder.set_title(env, &title)?;
}
let (responses, texts) = Self::texts_and_responses(env, self.btns, &self.cbtns)?;
let (tx, rx) = futures_channel::mpsc::unbounded::<MessageBoxResponse>();
let proxy = DynamicProxy::build(
env,
&LoaderContext::None,
[
DialogInterfaceOnClickListener::class_name(),
DialogInterfaceOnCancelListener::class_name(),
DialogInterfaceOnDismissListener::class_name(),
],
move |env, method, args| {
let name = method.get_name(env)?.try_to_string(env)?;
if name == "onCancel" {
tx.unbounded_send(MessageBoxResponse::Cancel).ok();
} else if name == "onDismiss" {
tx.unbounded_send(MessageBoxResponse::Close).ok();
} else if name == "onClick" {
let which = args.get_element(env, 1)?;
let which =
unsafe { JInteger::from_raw(env, which.into_raw()) }.value(env)?;
let response = match which {
-3 => responses[2].unwrap(),
-2 => responses[1].unwrap(),
-1 => responses[0].unwrap(),
_ => panic!("Unknown button index: {}", which),
};
tx.unbounded_send(response).ok();
}
Ok(JObject::null())
},
)?;
if responses[0].is_some() {
builder.set_positive_button(env, &texts[0], &proxy)?;
}
if responses[1].is_some() {
builder.set_negative_button(env, &texts[1], &proxy)?;
}
if responses[2].is_some() {
builder.set_neutral_button(env, &texts[2], &proxy)?;
}
let dialog = builder.create(env)?;
dialog.set_on_cancel_listener(env, &proxy)?;
dialog.set_on_dismiss_listener(env, &proxy)?;
dialog.show(env)?;
Result::Ok((rx, proxy))
})?;
Ok(async move {
rx.recv()
.await
.map_err(|e| Error::Io(std::io::Error::other(e)))
})
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CustomButton {
result: u16,
label: String,
}
impl CustomButton {
pub fn new(result: u16, label: impl AsRef<str>) -> Self {
Self {
result,
label: label.as_ref().to_string(),
}
}
}