use dioxus::prelude::*;
use std::fmt::Display;
#[cfg(target_arch = "wasm32")]
use async_std::task::sleep;
#[cfg(target_arch = "wasm32")]
use instant::{Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TimerState {
Inactive,
Working,
Finished,
Paused,
}
impl Display for TimerState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = match self {
TimerState::Inactive => "Inactive",
TimerState::Working => "Working",
TimerState::Finished => "Finished",
TimerState::Paused => "Paused",
};
write!(f, "{text}")
}
}
#[derive(Debug, Clone)]
pub struct DioxusTimer {
preset_duration: Duration,
target_time: Instant,
state: TimerState,
current_time: Instant,
paused_time: Option<Instant>,
}
impl DioxusTimer {
pub fn new() -> Self {
let current_time = Instant::now();
let target_time = current_time;
Self {
preset_duration: Duration::ZERO,
target_time,
state: TimerState::Inactive,
current_time,
paused_time: None,
}
}
pub fn set_preset_time(&mut self, preset_duration: Duration) {
if self.state == TimerState::Finished {
return;
}
self.preset_duration = preset_duration;
self.target_time = self
.current_time
.checked_add(preset_duration)
.unwrap_or(self.current_time);
}
pub fn remaining_time(&self) -> Duration {
self.target_time
.checked_duration_since(self.current_time)
.unwrap_or(Duration::ZERO)
}
pub fn state(&self) -> TimerState {
self.state
}
pub fn start(&mut self) {
match self.state {
TimerState::Inactive => {
if self.preset_duration.is_zero() {
return;
}
self.target_time = self
.current_time
.checked_add(self.preset_duration)
.unwrap_or(self.current_time);
self.state = TimerState::Working;
}
TimerState::Paused => {
self.state = TimerState::Working;
}
_ => {}
}
}
pub fn pause(&mut self) {
if let TimerState::Working = self.state {
self.state = TimerState::Paused;
self.paused_time = Some(Instant::now());
}
}
pub fn reset(&mut self) {
if self.state == TimerState::Finished {
self.state = TimerState::Inactive;
return;
}
self.target_time = self
.current_time
.checked_add(self.preset_duration)
.unwrap_or(self.current_time);
}
pub fn update(&mut self) {
self.current_time = Instant::now();
match self.state {
TimerState::Working => {
if self
.target_time
.checked_duration_since(self.current_time)
.is_none()
{
self.state = TimerState::Finished;
}
}
TimerState::Paused => {
self.target_time = self
.target_time
.checked_add(self.current_time - self.paused_time.unwrap())
.unwrap_or(self.current_time);
self.paused_time = Some(self.current_time);
}
TimerState::Inactive => {
self.reset();
}
_ => {}
}
}
}
impl Default for DioxusTimer {
fn default() -> Self {
Self::new()
}
}
impl Display for DioxusTimer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let rem_time = self.remaining_time().as_secs();
write!(
f,
"{:0>2}:{:0>2}:{:0>2}",
rem_time / 3600,
rem_time % 3600 / 60,
rem_time % 60,
)
}
}
pub fn use_timer(tick: Duration) -> Signal<DioxusTimer> {
let mut timer = use_signal(DioxusTimer::new);
use_future(move || async move {
loop {
timer.write().update();
sleep(tick).await;
}
});
timer
}