#[cfg(feature = "bluetooth")]
use crate::model::bluetooth::BluetoothEvent;
use crate::model::{InspectionState, Model, TimerState};
use crate::msg::{allowed_msg, Msg, INSPECTION_LIMIT_MS};
use crate::persistence;
pub(crate) fn update(model: &mut Model, msg: Msg) {
if matches!(msg, Msg::Tick) {
#[cfg(feature = "bluetooth")]
{
if model.show_bluetooth() {
model.poll_bluetooth();
}
if model.bluetooth_timer_active() {
model.poll_bluetooth_timer();
}
}
}
if !allowed_msg(model, msg) {
return;
}
match msg {
Msg::Press => handle_press(model),
Msg::Release => handle_release(model),
Msg::Reset => handle_reset(model),
Msg::Tick => handle_tick(model),
Msg::SelectUp => handle_select_up(model),
Msg::SelectDown => handle_select_down(model),
Msg::NextEvent => handle_next_event(model),
Msg::PrevEvent => handle_prev_event(model),
Msg::NextSession => handle_next_session(model),
Msg::PrevSession => handle_prev_session(model),
Msg::NewSession => handle_new_session(model),
Msg::DeleteSession => handle_delete_session(model),
Msg::NextScramble => handle_next_scramble(model),
Msg::Help => handle_help(model),
Msg::ToggleInspection => handle_toggle_inspection(model),
Msg::OpenDetails => handle_open_details(model),
Msg::CloseDetails => handle_close_details(model),
Msg::OpenDetailedStats => handle_open_detailed_stats(model),
Msg::DeleteTime => handle_delete_time(model),
Msg::NavLeft => handle_nav_left(model),
Msg::NavRight => handle_nav_right(model),
Msg::ToggleFocus => handle_toggle_focus(model),
#[cfg(feature = "bluetooth")]
Msg::ToggleBluetooth => handle_toggle_bluetooth(model),
#[cfg(feature = "bluetooth")]
Msg::DisconnectBluetooth => handle_disconnect_bluetooth(model),
Msg::ToggleZen => handle_toggle_zen(model),
Msg::Quit => {}
}
}
fn handle_press(model: &mut Model) {
if model.show_details() {
if model.timer_state() == TimerState::Idle {
let modifier = model.selected_details_modifier();
model.history_mut().set_modifier(modifier);
persistence::save(model);
}
return;
}
#[cfg(feature = "bluetooth")]
if model.bluetooth_connected() {
return;
}
match model.timer_state() {
TimerState::Idle => {
if model.inspection_enabled() {
model.start_inspection();
} else {
model.set_timer_state(TimerState::Pulsed);
}
}
TimerState::Pulsed | TimerState::Inspection(InspectionState::Pulsed(_)) => {}
TimerState::Inspection(InspectionState::Running(_)) => model.pulse_timer(),
TimerState::Running(start) => {
let elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap();
model.set_last_time_ms(elapsed_ms);
let event = model.event();
let scramble = model.scramble().to_string();
model.history_mut().add_ms(elapsed_ms, event, scramble);
model.stop_timer();
model.next_scramble();
persistence::save(model);
}
}
}
fn handle_release(model: &mut Model) {
#[cfg(feature = "bluetooth")]
if model.bluetooth_connected() {
return;
}
if model.show_details() {
return;
}
if matches!(
model.timer_state(),
TimerState::Pulsed | TimerState::Inspection(InspectionState::Pulsed(_))
) {
model.start_timer();
}
}
fn handle_reset(model: &mut Model) {
model.reset_timer();
}
fn handle_tick(model: &mut Model) {
if let TimerState::Inspection(InspectionState::Running(start)) = model.timer_state() {
let elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap();
if elapsed_ms >= INSPECTION_LIMIT_MS {
model.set_last_time_ms(INSPECTION_LIMIT_MS);
model.set_timer_state(TimerState::Inspection(InspectionState::Pulsed(start)));
}
}
}
fn handle_select_up(model: &mut Model) {
if model.show_help() {
model.scroll_help_up();
return;
}
#[cfg(feature = "bluetooth")]
if model.show_bluetooth() {
model.bluetooth_select_up();
return;
}
if model.show_mean_detail() {
model.mean_detail_select_up();
} else if model.show_detailed_stats() {
model.detailed_stats_select_up();
} else if model.show_details() {
model.prev_details_modifier();
} else if model.main_focus_is_stats() {
model.main_stats_select_up();
} else {
model.history_mut().select_previous();
}
}
fn handle_select_down(model: &mut Model) {
if model.show_help() {
model.scroll_help_down();
return;
}
#[cfg(feature = "bluetooth")]
if model.show_bluetooth() {
model.bluetooth_select_down();
return;
}
if model.show_mean_detail() {
model.mean_detail_select_down();
} else if model.show_detailed_stats() {
model.detailed_stats_select_down();
} else if model.show_details() {
model.next_details_modifier();
} else if model.main_focus_is_stats() {
model.main_stats_select_down();
} else {
model.history_mut().select_next();
}
}
fn handle_toggle_focus(model: &mut Model) {
if model.show_help() || model.show_details() || model.show_detailed_stats() {
return;
}
#[cfg(feature = "bluetooth")]
if model.show_bluetooth() {
return;
}
model.toggle_main_focus();
}
fn handle_next_event(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.next_event();
}
}
fn handle_prev_event(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.prev_event();
}
}
fn handle_next_session(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.next_session();
if model.get_current_session().scramble.is_none() {
model.next_scramble();
}
}
}
fn handle_prev_session(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.prev_session();
if model.get_current_session().scramble.is_none() {
model.next_scramble();
}
}
}
fn handle_new_session(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.add_session();
model.next_scramble();
persistence::save(model);
}
}
fn handle_delete_session(model: &mut Model) {
if model.timer_state() == TimerState::Idle && model.delete_current_session() {
if model.get_current_session().scramble.is_none() {
model.next_scramble();
}
persistence::save(model);
}
}
fn handle_next_scramble(model: &mut Model) {
if model.timer_state() == TimerState::Idle {
model.next_scramble();
}
}
fn handle_help(model: &mut Model) {
model.toggle_help();
}
#[cfg(feature = "bluetooth")]
fn handle_toggle_bluetooth(model: &mut Model) {
if model.show_help() || model.show_details() || model.show_detailed_stats() {
return;
}
if let Some(tx) = model.toggle_bluetooth() {
use crate::bluetooth::timer::{get_adapter, get_devices};
use futures_util::StreamExt;
std::thread::spawn(move || {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
let _ = tx.send(BluetoothEvent::Error("Failed to create runtime".into()));
return;
};
runtime.block_on(async move {
let adapter = match get_adapter().await {
Ok(adapter) => adapter,
Err(err) => {
let _ = tx.send(BluetoothEvent::Error(err.to_string()));
return;
}
};
let _ = tx.send(BluetoothEvent::Adapter(adapter.clone()));
let _ = tx.send(BluetoothEvent::Status("Scanning for devices...".into()));
let mut stream = match get_devices(&adapter).await {
Ok(stream) => stream,
Err(err) => {
let _ = tx.send(BluetoothEvent::Error(err.to_string()));
return;
}
};
while let Some(device) = stream.next().await {
if tx.send(BluetoothEvent::Device(device)).is_err() {
break;
}
}
});
});
}
}
#[cfg(feature = "bluetooth")]
fn handle_disconnect_bluetooth(model: &mut Model) {
if (model.bluetooth_connected() || model.bluetooth_connecting())
&& let Some((tx, rx, adapter)) = model.disconnect_bluetooth()
{
restart_bluetooth_scan(tx, rx, adapter);
}
}
#[cfg(feature = "bluetooth")]
fn restart_bluetooth_scan(
tx: flume::Sender<BluetoothEvent>,
_rx: flume::Receiver<BluetoothEvent>,
adapter: btleplug::platform::Adapter,
) {
use crate::bluetooth::timer::get_devices;
use futures_util::StreamExt;
std::thread::spawn(move || {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
return;
};
runtime.block_on(async move {
let Ok(mut stream) = get_devices(&adapter).await else {
return;
};
while let Some(device) = stream.next().await {
if tx.send(BluetoothEvent::Device(device)).is_err() {
break;
}
}
});
});
}
#[cfg(feature = "bluetooth")]
fn handle_bluetooth_connect(model: &mut Model) {
use crate::bluetooth::timer::{TimerState as BtTimerState, connect, disconnect};
use futures_util::StreamExt;
let Some(device) = model.bluetooth_selected_device().cloned() else {
return;
};
let Some((tx, cancel_rx, adapter, conn_tx)) = model.connect_bluetooth_device() else {
return;
};
let device_id = device.id;
std::thread::spawn(move || {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
let _ = tx.send(BtTimerState::Disconnected);
return;
};
runtime.block_on(async move {
let mut stream = match connect(&device_id, &adapter).await {
Ok(s) => s,
Err(e) => {
let _ = tx.send(BtTimerState::Error(e.to_string()));
let _ = tx.send(BtTimerState::Disconnected);
return;
}
};
let _ = conn_tx.send(());
loop {
tokio::select! {
state = stream.next() => match state {
Some(state) => { if tx.send(state).is_err() { break; } }
None => break,
},
_ = cancel_rx.recv_async() => break,
}
}
let _ = disconnect(&device_id, &adapter).await;
let _ = tx.send(BtTimerState::Disconnected);
});
});
}
fn handle_toggle_inspection(model: &mut Model) {
model.toggle_inspection();
persistence::save_config(*model.settings());
}
fn handle_toggle_zen(model: &mut Model) {
model.toggle_zen();
persistence::save_config(*model.settings());
}
fn handle_open_details(model: &mut Model) {
#[cfg(feature = "bluetooth")]
if model.show_bluetooth() {
if model.bluetooth_connected() {
handle_disconnect_bluetooth(model);
} else {
handle_bluetooth_connect(model);
}
return;
}
if model.show_mean_detail() {
model.open_details_for_selected_mean_time();
return;
}
if model.show_detailed_stats() && !model.show_mean_detail() {
model.open_mean_detail();
return;
}
if model.main_focus_is_stats() {
model.open_mean_detail_from_stats();
return;
}
if model.timer_state() == TimerState::Idle && !model.history().is_empty() {
model.open_details();
}
}
fn handle_open_detailed_stats(model: &mut Model) {
if model.timer_state() == TimerState::Idle && !model.history().is_empty() {
model.open_detailed_stats();
}
}
#[allow(clippy::missing_const_for_fn)]
fn handle_close_details(model: &mut Model) {
#[cfg(feature = "bluetooth")]
if model.show_bluetooth() {
model.close_bluetooth();
return;
}
model.close_current_screen();
}
fn handle_delete_time(model: &mut Model) {
if model.timer_state() == TimerState::Idle && !model.history().is_empty() {
model.history_mut().delete_selected();
persistence::save(model);
if model.show_details() && model.history().is_empty() {
model.close_current_screen();
}
}
}
fn handle_nav_left(model: &mut Model) {
if model.show_detailed_stats() && !model.show_mean_detail() {
model.detailed_stats_col_left();
} else if model.main_focus_is_stats() {
model.main_stats_col_left();
} else if model.show_details() {
model.details_nav_prev();
}
}
fn handle_nav_right(model: &mut Model) {
if model.show_detailed_stats() && !model.show_mean_detail() {
model.detailed_stats_col_right();
} else if model.main_focus_is_stats() {
model.main_stats_col_right();
} else if model.show_details() {
model.details_nav_next();
}
}