diff --git a/src-tauri/src/application_state.rs b/src-tauri/src/application_state.rs index 29d021c..5b6bf40 100644 --- a/src-tauri/src/application_state.rs +++ b/src-tauri/src/application_state.rs @@ -1,4 +1,4 @@ -use std::{fmt, path::PathBuf}; +use std::{fmt, path::PathBuf, time::Instant}; use rand::seq::SliceRandom; use sqlx::{Pool, Sqlite}; @@ -40,7 +40,7 @@ pub struct ApplicationState { pub tick_process: Option>, // TODO button on frontend to open this location _data_location: PathBuf, - next_session_in: i64, + timer_start: Instant, paused: bool, session_count: u8, settings: ModelSettings, @@ -69,7 +69,7 @@ impl ApplicationState { _data_location: local_dir, session_count: 0, strategies, - next_session_in: settings.session_as_sec, + timer_start: std::time::Instant::now(), paused: false, session_status: SessionStatus::Work, settings, @@ -88,12 +88,12 @@ impl ApplicationState { } /// fuzzy second to minutes conversion - fn format_sec_to_min(sec: i64) -> String { - let minutes = (sec / 60) % 60; - if minutes > 1 { - format!("{} minutes", minutes) - } else { + fn format_sec_to_min(sec: u16) -> String { + if sec < 60 { String::from("less than 1 minute") + } else { + let minutes = (f64::try_from(sec).unwrap_or(0.0) / 60.0).round(); + format!("{minutes} minutes") } } @@ -106,8 +106,27 @@ impl ApplicationState { } /// Get the settings for starting a break - pub fn get_break_settings(&self) -> (i64, String) { - (self.next_session_in, self.random_strategy()) + pub fn get_break_settings(&self) -> (u16, String) { + (self.current_timer_left(), self.random_strategy()) + } + + // Return, in seconds, the current amount left of the onoing session, or break + pub fn current_timer_left(&self) -> u16 { + let taken_since = u16::try_from( + std::time::Instant::now() + .duration_since(self.timer_start) + .as_secs(), + ) + .unwrap_or(0); + match self.session_status { + SessionStatus::Break(break_type) => match break_type { + Break::Short => { + u16::from(self.settings.short_break_as_sec).saturating_sub(taken_since) + } + Break::Long => self.settings.long_break_as_sec.saturating_sub(taken_since), + }, + SessionStatus::Work => self.settings.session_as_sec.saturating_sub(taken_since), + } } /// Toggle the pause status @@ -120,10 +139,13 @@ impl ApplicationState { self.paused } + pub fn reset_timer(&mut self) { + self.timer_start = std::time::Instant::now(); + } /// Start the timer, by saetting the next_break_in value pub fn start_work_session(&mut self) { self.session_status = SessionStatus::Work; - self.next_session_in = self.settings.session_as_sec; + self.reset_timer(); } /// Start the break session @@ -131,13 +153,12 @@ impl ApplicationState { pub fn start_break_session(&mut self) { let break_length = if self.session_count < self.settings.number_session_before_break { self.session_count += 1; - self.next_session_in = self.settings.short_break_as_sec; Break::Short } else { - self.next_session_in = self.settings.long_break_as_sec; self.session_count = 0; Break::Long }; + self.reset_timer(); self.session_status = SessionStatus::Break(break_length); } @@ -155,8 +176,8 @@ impl ApplicationState { let number_before_long = self.get_session_before_long_break(); let mut title = String::from("next long break after "); match number_before_long { - x if x > 1 => title.push_str(&format!("{} sessions", number_before_long)), - x if x == 1 => title.push_str(&format!("{} session", number_before_long)), + 2.. => title.push_str(&format!("{number_before_long} sessions")), + 1 => title.push_str(&format!("{number_before_long} session")), _ => title.push_str("current session"), } title @@ -166,28 +187,10 @@ impl ApplicationState { pub fn get_next_break_title(&self) -> String { format!( "next break in {}", - Self::format_sec_to_min(self.next_session_in) + Self::format_sec_to_min(self.current_timer_left()) ) } - /// Get time left in the current Break/Session, to update the frontend timer & progress circular bar - pub const fn get_next_session(&self) -> i64 { - self.next_session_in - } - - /// When resetting the tick proccess, need to match next_session_in to the value in the ModelSettings object - pub fn reset_next_session_in(&mut self) { - self.next_session_in = self.settings.session_as_sec; - } - - /// Reduce the next_session_in value by 1, and return the new value, this assumes that it is called every 1 second, - pub fn tick(&mut self) -> i64 { - if self.next_session_in > 0 { - self.next_session_in -= 1; - } - self.next_session_in - } - /// Return ModelSettings object pub const fn get_settings(&self) -> ModelSettings { self.settings @@ -209,7 +212,7 @@ impl ApplicationState { } /// Get long_break setting value - pub fn set_long_break_as_sec(&mut self, i: i64) { + pub fn set_long_break_as_sec(&mut self, i: u16) { self.settings.long_break_as_sec = i; } @@ -219,12 +222,12 @@ impl ApplicationState { } /// Set session setting value - pub fn set_session_as_sec(&mut self, i: i64) { + pub fn set_session_as_sec(&mut self, i: u16) { self.settings.session_as_sec = i; } /// Set short_break setting value - pub fn set_short_break_as_sec(&mut self, i: i64) { + pub fn set_short_break_as_sec(&mut self, i: u8) { self.settings.short_break_as_sec = i; } diff --git a/src-tauri/src/db/models/settings.rs b/src-tauri/src/db/models/settings.rs index f45df24..205b67d 100644 --- a/src-tauri/src/db/models/settings.rs +++ b/src-tauri/src/db/models/settings.rs @@ -3,13 +3,13 @@ use sqlx::{FromRow, Pool, Sqlite}; use crate::app_error::AppError; -const ONE_MINUTE_AS_SEC: i64 = 60; +const ONE_MINUTE_AS_SEC: u16 = 60; #[derive(FromRow, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] pub struct ModelSettings { - pub short_break_as_sec: i64, - pub long_break_as_sec: i64, - pub session_as_sec: i64, + pub short_break_as_sec: u8, + pub long_break_as_sec: u16, + pub session_as_sec: u16, pub number_session_before_break: u8, pub fullscreen: bool, } @@ -36,21 +36,21 @@ impl ModelSettings { } /// Update shortbreak setting - pub async fn update_shortbreak(sqlite: &Pool, shortbreak: i64) -> Result<(), AppError> { + pub async fn update_shortbreak(sqlite: &Pool, shortbreak: u8) -> Result<(), AppError> { let query = "UPDATE Settings SET short_break_as_sec = $1"; sqlx::query(query).bind(shortbreak).execute(sqlite).await?; Ok(()) } /// Update long break setting - pub async fn update_longbreak(sqlite: &Pool, longbreak: i64) -> Result<(), AppError> { + pub async fn update_longbreak(sqlite: &Pool, longbreak: u16) -> Result<(), AppError> { let query = "UPDATE Settings SET long_break_as_sec = $1"; sqlx::query(query).bind(longbreak).execute(sqlite).await?; Ok(()) } /// Update session length - pub async fn update_session(sqlite: &Pool, session: i64) -> Result<(), AppError> { + pub async fn update_session(sqlite: &Pool, session: u16) -> Result<(), AppError> { let query = "UPDATE Settings SET session_as_sec = $1"; sqlx::query(query).bind(session).execute(sqlite).await?; Ok(()) diff --git a/src-tauri/src/internal_message_handler/mod.rs b/src-tauri/src/internal_message_handler/mod.rs index 19ddb8d..ca757ed 100644 --- a/src-tauri/src/internal_message_handler/mod.rs +++ b/src-tauri/src/internal_message_handler/mod.rs @@ -39,10 +39,10 @@ impl Default for PackageInfo { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] pub enum SettingChange { FullScreen(bool), - LongBreakLength(i64), + LongBreakLength(u16), NumberSessions(u8), - ShortBreakLength(i64), - SessionLength(i64), + ShortBreakLength(u8), + SessionLength(u16), Reset, } @@ -67,6 +67,7 @@ pub enum Emitter { PackageInfo, Paused, SendError, + SendAutoStart(bool), SendSettings, NextBreak, SessionsBeforeLong, @@ -231,7 +232,7 @@ fn update_menu( /// Stop the tick process, and start a new one fn reset_timer(state: &Arc>, sx: &Sender) { - state.lock().reset_next_session_in(); + state.lock().reset_timer(); tick_process(state, sx.clone()); } @@ -354,14 +355,27 @@ fn handle_emitter(app: &AppHandle, emitter: Emitter, state: &Arc { + let on_break = state.lock().on_break(); + if !on_break { + app.emit_to( + ObliqoroWindow::Main.as_str(), + EmitMessages::AutoStart.as_str(), + value, + ) + .unwrap_or(()); + } + } + Emitter::SendError => { app.emit_to( ObliqoroWindow::Main.as_str(), - EmitMessages::SendError.as_str(), + EmitMessages::Error.as_str(), "Internal Error", ) .unwrap_or(()); @@ -459,7 +473,7 @@ pub fn start_message_handler( InternalMessage::ChangeSetting(setting_change) => { if let Err(e) = handle_settings(setting_change, &state, &sx).await { - error!("{}", e); + error!("{:#?}", e); sx.send(InternalMessage::Emit(Emitter::SendError)) .unwrap_or_default(); } diff --git a/src-tauri/src/request_handlers/messages.rs b/src-tauri/src/request_handlers/messages.rs index 8cc53ce..64932ef 100644 --- a/src-tauri/src/request_handlers/messages.rs +++ b/src-tauri/src/request_handlers/messages.rs @@ -9,17 +9,18 @@ pub enum EmitMessages { NextBreak, OnBreak, Paused, - SendError, + Error, + AutoStart, SessionsBeforeLong, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct ShowTimer { - interval: i64, + interval: u16, strategy: String, } impl ShowTimer { - pub const fn new(interval: i64, strategy: String) -> Self { + pub const fn new(interval: u16, strategy: String) -> Self { Self { interval, strategy } } } @@ -33,7 +34,8 @@ impl EmitMessages { Self::GoToTimer => "goto::timer", Self::NextBreak => "next-break", Self::OnBreak => "on-break", - Self::SendError => "error", + Self::Error => "error", + Self::AutoStart => "autostart", Self::SessionsBeforeLong => "sessions-before-long", Self::PackageInfo => "package-info", Self::Paused => "paused", diff --git a/src-tauri/src/tick/mod.rs b/src-tauri/src/tick/mod.rs index 9ea3b66..9aca5cf 100644 --- a/src-tauri/src/tick/mod.rs +++ b/src-tauri/src/tick/mod.rs @@ -9,18 +9,19 @@ use crate::{ const ONE_SECOND_AS_MS: u64 = 1000; const ONE_MINUTE_AS_MS: u64 = ONE_SECOND_AS_MS * 60; -/// Spawn off a tokio thread, that loops continually, is essentially the internal timer that powers the whole application -/// Is save in the state struct, so that it can be aborted when settings change etc + +/// Spawn off a tokio thread, that loops continually, well with a 250ms pause between each loop +/// The timer checking is then spawned off into another thread +/// The outer tread is saved into ApplicationState, so that it can be cancelled at any time pub fn tick_process(state: &Arc>, sx: Sender) { if let Some(x) = &state.lock().tick_process { x.abort(); } - let mut last_updated = std::time::Instant::now(); - let mut menu_updated = std::time::Instant::now(); + let menu_updated = Arc::new(Mutex::new(std::time::Instant::now())); let spawn_state = Arc::clone(state); - spawn_state + state .lock() .sx .send(InternalMessage::UpdateMenuTimer) @@ -28,51 +29,49 @@ pub fn tick_process(state: &Arc>, sx: Sender= 1 { - let to_run = spawn_state.lock().session_status; - let tick_count = spawn_state.lock().tick(); - match to_run { - SessionStatus::Break(_) => { - spawn_state - .lock() - .sx - .send(InternalMessage::Emit(Emitter::OnBreak)) - .unwrap_or_default(); - if tick_count < 1 { - spawn_state - .lock() - .sx - .send(InternalMessage::Break(BreakMessage::End)) - .unwrap_or_default(); - } - } - SessionStatus::Work => { - if menu_updated.elapsed().as_millis() >= u128::from(ONE_MINUTE_AS_MS) { + if !paused { + let spawn_state = Arc::clone(&spawn_state); + let sx = sx.clone(); + let menu_updated = Arc::clone(&menu_updated); + + tokio::spawn(async move { + let to_run = spawn_state.lock().session_status; + let session_time_left = spawn_state.lock().current_timer_left(); + match to_run { + SessionStatus::Break(_) => { spawn_state .lock() .sx - .send(InternalMessage::UpdateMenuTimer) + .send(InternalMessage::Emit(Emitter::OnBreak)) .unwrap_or_default(); - menu_updated = std::time::Instant::now(); + if session_time_left < 1 { + spawn_state + .lock() + .sx + .send(InternalMessage::Break(BreakMessage::End)) + .unwrap_or_default(); + } } - if tick_count < 1 { - sx.send(InternalMessage::Break(BreakMessage::Start)) - .unwrap_or_default(); + SessionStatus::Work => { + if menu_updated.lock().elapsed().as_millis() + >= u128::from(ONE_MINUTE_AS_MS) + { + spawn_state + .lock() + .sx + .send(InternalMessage::UpdateMenuTimer) + .unwrap_or_default(); + *menu_updated.lock() = std::time::Instant::now(); + } + if session_time_left < 1 { + sx.send(InternalMessage::Break(BreakMessage::Start)) + .unwrap_or_default(); + } } } - } - } - last_updated = std::time::Instant::now(); - - let elapsed = u64::try_from(last_updated.elapsed().as_millis()).unwrap_or(0); - let to_sleep = if ONE_SECOND_AS_MS >= elapsed { - ONE_SECOND_AS_MS - elapsed - } else { - ONE_SECOND_AS_MS - }; - if to_sleep <= ONE_SECOND_AS_MS { - tokio::time::sleep(std::time::Duration::from_millis(to_sleep)).await; + }); } + tokio::time::sleep(std::time::Duration::from_millis(250)).await; } })); }