diff options
author | Ricky Kresslein <rk@lakoliu.com> | 2022-04-28 19:18:15 +0300 |
---|---|---|
committer | Ricky Kresslein <rk@lakoliu.com> | 2022-04-28 19:18:15 +0300 |
commit | 8ee461ce5c9cdd117b0e4b9f8dfec0a388d37f29 (patch) | |
tree | 43d3d25d80cc00f2855a3631faced64da1f440ab /src | |
parent | 794cf5b7025d72b9a2b61fc6f5df753363d6060a (diff) | |
download | Furtherance-8ee461ce5c9cdd117b0e4b9f8dfec0a388d37f29.tar.zst |
Add Pomodoro timer
Diffstat (limited to 'src')
-rwxr-xr-x | src/application.rs | 58 | ||||
-rwxr-xr-x | src/gtk/preferences_window.ui | 37 | ||||
-rwxr-xr-x | src/ui/preferences_window.rs | 29 | ||||
-rwxr-xr-x | src/ui/window.rs | 178 |
4 files changed, 252 insertions, 50 deletions
diff --git a/src/application.rs b/src/application.rs index 06a033b..cc9998b 100755 --- a/src/application.rs +++ b/src/application.rs @@ -32,7 +32,7 @@ mod imp { #[derive(Debug, Default)] pub struct FurtheranceApplication { - pub idle_dialog: Mutex<gtk::MessageDialog>, + pub pomodoro_dialog: Mutex<gtk::MessageDialog>, } #[glib::object_subclass] @@ -122,10 +122,7 @@ impl FurtheranceApplication { let window = FurtheranceWindow::default(); let imp = window.imp(); if *imp.running.lock().unwrap() && *imp.idle_time_reached.lock().unwrap() { - window.set_subtract_idle(true); - imp.start_button.emit_clicked(); - let imp_app = imp::FurtheranceApplication::from_instance(&app); - imp_app.idle_dialog.lock().unwrap().close(); + window.imp().idle_dialog.lock().unwrap().response(gtk::ResponseType::Reject); } })); self.add_action(&discard_idle_action); @@ -134,12 +131,24 @@ impl FurtheranceApplication { continue_idle_action.connect_activate(clone!(@weak self as app => move |_, _| { let window = FurtheranceWindow::default(); if *window.imp().running.lock().unwrap() { - window.reset_vars(); - let imp = imp::FurtheranceApplication::from_instance(&app); - imp.idle_dialog.lock().unwrap().close(); + window.imp().idle_dialog.lock().unwrap().response(gtk::ResponseType::Accept); } })); self.add_action(&continue_idle_action); + + let continue_pomodoro_action = gio::SimpleAction::new("continue-pomodoro-action", None); + continue_pomodoro_action.connect_activate(clone!(@weak self as app => move |_, _| { + let imp = imp::FurtheranceApplication::from_instance(&app); + imp.pomodoro_dialog.lock().unwrap().response(gtk::ResponseType::Accept); + })); + self.add_action(&continue_pomodoro_action); + + let stop_pomodoro_action = gio::SimpleAction::new("stop-pomodoro-action", None); + stop_pomodoro_action.connect_activate(clone!(@weak self as app => move |_, _| { + let imp = imp::FurtheranceApplication::from_instance(&app); + imp.pomodoro_dialog.lock().unwrap().response(gtk::ResponseType::Reject); + })); + self.add_action(&stop_pomodoro_action); } fn setup_application(&self) { @@ -233,9 +242,7 @@ impl FurtheranceApplication { } } - pub fn system_notification(&self, title: &str, subtitle: &str, dialog: gtk::MessageDialog) { - let imp = imp::FurtheranceApplication::from_instance(self); - *imp.idle_dialog.lock().unwrap() = dialog; + pub fn system_idle_notification(&self, title: &str, subtitle: &str) { let icon = Some("appointment-missed-symbolic"); let notification = gio::Notification::new(title.as_ref()); notification.set_body(Some(subtitle.as_ref())); @@ -250,9 +257,32 @@ impl FurtheranceApplication { notification.add_button(&gettext("Discard"), "app.discard-idle-action"); notification.add_button(&gettext("Continue"), "app.continue-idle-action"); - gio::Application::default() - .unwrap() - .send_notification(None, ¬ification); + notification.set_priority(gio::NotificationPriority::High); + + self.send_notification(Some("idle"), ¬ification); + } + + pub fn system_pomodoro_notification(&self, dialog: gtk::MessageDialog) { + let imp = imp::FurtheranceApplication::from_instance(self); + *imp.pomodoro_dialog.lock().unwrap() = dialog; + let icon = Some("alarm-symbolic"); + let notification = gio::Notification::new("Time's up!"); + notification.set_body(Some("Your Furtherance timer ended.")); + + if let Some(icon) = icon { + match gio::Icon::for_string(icon) { + Ok(gicon) => notification.set_icon(&gicon), + Err(err) => debug!("Unable to display notification: {:?}", err), + } + } + + notification.add_button(&gettext("Continue"), "app.continue-pomodoro-action"); + notification.add_button(&gettext("Stop"), "app.stop-pomodoro-action"); + + notification.set_priority(gio::NotificationPriority::High); + + self.withdraw_notification("idle"); + self.send_notification(Some("pomodoro"), ¬ification); } } diff --git a/src/gtk/preferences_window.ui b/src/gtk/preferences_window.ui index b3b12a8..58296bb 100755 --- a/src/gtk/preferences_window.ui +++ b/src/gtk/preferences_window.ui @@ -128,7 +128,6 @@ </child> </object> </child> - <child> <object class="AdwActionRow"> <property name="title" translatable="yes">Show tags</property> @@ -142,7 +141,41 @@ </child> </object> </child> - + </object> + </child> + <child> + <object class="AdwPreferencesGroup" id="timer_group"> + <property name="title" translatable="yes">Timer</property> + <property name="visible">True</property> + <child> + <object class="AdwExpanderRow" id="pomodoro_expander"> + <property name="title" translatable="yes">_Pomodoro</property> + <property name="subtitle" translatable="yes">Timer counts down instead of up.</property> + <property name="show_enable_switch">True</property> + <property name="use_underline">True</property> + <child> + <object class="AdwActionRow"> + <property name="title" translatable="yes">Interval</property> + <property name="subtitle" translatable="yes">Timer start time in minutes</property> + <property name="use_underline">True</property> + <child> + <object class="GtkSpinButton" id="pomodoro_spin"> + <property name="valign">center</property> + <property name="adjustment"> + <object class="GtkAdjustment"> + <property name="upper">999</property> + <property name="lower">1</property> + <property name="step_increment">1</property> + <property name="page_increment">15</property> + </object> + </property> + <property name="numeric">True</property> + </object> + </child> + </object> + </child> + </object> + </child> </object> </child> </object> diff --git a/src/ui/preferences_window.rs b/src/ui/preferences_window.rs index 47a7adc..6436026 100755 --- a/src/ui/preferences_window.rs +++ b/src/ui/preferences_window.rs @@ -57,6 +57,13 @@ mod imp { pub show_daily_sums_switch: TemplateChild<gtk::Switch>, #[template_child] pub show_tags_switch: TemplateChild<gtk::Switch>, + + #[template_child] + pub timer_group: TemplateChild<adw::PreferencesGroup>, + #[template_child] + pub pomodoro_expander: TemplateChild<adw::ExpanderRow>, + #[template_child] + pub pomodoro_spin: TemplateChild<gtk::SpinButton>, } #[glib::object_subclass] @@ -175,6 +182,18 @@ impl FurPreferencesWindow { "active" ); + settings_manager::bind_property( + "pomodoro", + &*imp.pomodoro_expander, + "enable-expansion" + ); + + settings_manager::bind_property( + "pomodoro-time", + &*imp.pomodoro_spin, + "value" + ); + imp.dark_theme_switch.connect_active_notify(move |_|{ let app = FurtheranceApplication::default(); app.update_light_dark(); @@ -204,6 +223,16 @@ impl FurPreferencesWindow { let window = FurtheranceWindow::default(); window.reset_history_box(); }); + + imp.pomodoro_expander.connect_enable_expansion_notify(move |_|{ + let window = FurtheranceWindow::default(); + window.refresh_timer(); + }); + + imp.pomodoro_spin.connect_value_changed(move |_|{ + let window = FurtheranceWindow::default(); + window.refresh_timer(); + }); } } diff --git a/src/ui/window.rs b/src/ui/window.rs index 2d3f9d4..2c87d78 100755 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -60,6 +60,8 @@ mod imp { pub subtract_idle: Mutex<bool>, pub idle_start_time: Mutex<String>, pub running: Mutex<bool>, + pub pomodoro_continue: Mutex<bool>, + pub idle_dialog: Mutex<gtk::MessageDialog>, } #[glib::object_subclass] @@ -120,7 +122,7 @@ impl FurtheranceWindow { } } - fn save_task(&self, start_time: DateTime<Local>, mut stop_time: DateTime<Local>) { + pub fn save_task(&self, start_time: DateTime<Local>, mut stop_time: DateTime<Local>) { // Save the most recent task to the database and clear the task_input field let imp = imp::FurtheranceWindow::from_instance(self); @@ -149,6 +151,7 @@ impl FurtheranceWindow { let _ = database::db_write(task_name.trim(), start_time, stop_time, tag_list); imp.task_input.set_text(""); imp.history_box.create_tasks_page(); + self.reset_idle(); } pub fn reset_history_box(&self) { @@ -175,14 +178,15 @@ impl FurtheranceWindow { self.add_css_class("devel"); } + *imp.pomodoro_continue.lock().unwrap() = false; imp.start_button.set_sensitive(false); imp.start_button.add_css_class("suggested-action"); + self.refresh_timer(); imp.task_input.grab_focus(); } fn setup_signals(&self) { let imp = imp::FurtheranceWindow::from_instance(self); - // running = false *imp.running.lock().unwrap() = false; let start_time = Rc::new(RefCell::new(Local::now())); let stop_time = Rc::new(RefCell::new(Local::now())); @@ -201,37 +205,87 @@ impl FurtheranceWindow { imp.start_button.connect_clicked(clone!(@weak self as this => move |button| { let imp2 = imp::FurtheranceWindow::from_instance(&this); if !*imp2.running.lock().unwrap() { - let mut secs: u32 = 0; - let mut mins: u32 = 0; - let mut hrs: u32 = 0; - - *imp2.running.lock().unwrap() = true; - *start_time.borrow_mut() = Local::now(); - imp2.task_input.set_sensitive(false); - let duration = Duration::new(1,0); - timeout_add_local(duration, clone!(@strong this as this_clone => move || { - let imp3 = imp::FurtheranceWindow::from_instance(&this_clone); - if *imp3.running.lock().unwrap() { - secs += 1; - if secs > 59 { - secs = 0; - mins += 1; - if mins > 59 { - mins = 0; - hrs += 1; + if settings_manager::get_bool("pomodoro") && !*imp2.pomodoro_continue.lock().unwrap() { + let mut secs: i32 = 0; + let mut mins: i32 = settings_manager::get_int("pomodoro-time"); + let mut hrs: i32 = mins / 60; + mins = mins % 60; + + *imp2.running.lock().unwrap() = true; + *start_time.borrow_mut() = Local::now(); + let timer_start = *start_time.borrow(); + imp2.task_input.set_sensitive(false); + let duration = Duration::new(1,0); + timeout_add_local(duration, clone!(@strong this as this_clone => move || { + let imp3 = imp::FurtheranceWindow::from_instance(&this_clone); + if *imp3.running.lock().unwrap() { + secs -= 1; + if secs < 0 { + secs = 59; + mins -= 1; + if mins < 0 { + mins = 59; + hrs -= 1; + } } + let watch_text: &str = &format!("{:02}:{:02}:{:02}", hrs, mins, secs).to_string(); + this_clone.set_watch_time(watch_text); } + if hrs == 0 && mins == 0 && secs == 0 { + let timer_stop = Local::now(); + *imp3.running.lock().unwrap() = false; + this_clone.pomodoro_over(timer_start, timer_stop); + } + Continue(*imp3.running.lock().unwrap()) + })); + } else { + let mut secs: u32 = 0; + let mut mins: u32 = 0; + let mut hrs: u32 = 0; + + if *imp2.pomodoro_continue.lock().unwrap() { + let pomodoro_start_time = *start_time.borrow(); + let now_time = Local::now(); + let continue_time = now_time - pomodoro_start_time; + let continue_time = continue_time.num_seconds() as u32; + hrs = continue_time / 3600; + mins = continue_time % 3600 / 60; + secs = continue_time % 60; let watch_text: &str = &format!("{:02}:{:02}:{:02}", hrs, mins, secs).to_string(); - this_clone.set_watch_time(watch_text); + this.set_watch_time(watch_text); + + *imp2.pomodoro_continue.lock().unwrap() = false; + } else { + *start_time.borrow_mut() = Local::now(); } - Continue(*imp3.running.lock().unwrap()) - })); + + *imp2.running.lock().unwrap() = true; + imp2.task_input.set_sensitive(false); + let duration = Duration::new(1,0); + timeout_add_local(duration, clone!(@strong this as this_clone => move || { + let imp3 = imp::FurtheranceWindow::from_instance(&this_clone); + if *imp3.running.lock().unwrap() { + secs += 1; + if secs > 59 { + secs = 0; + mins += 1; + if mins > 59 { + mins = 0; + hrs += 1; + } + } + let watch_text: &str = &format!("{:02}:{:02}:{:02}", hrs, mins, secs).to_string(); + this_clone.set_watch_time(watch_text); + } + Continue(*imp3.running.lock().unwrap()) + })); + } button.set_icon_name("media-playback-stop-symbolic"); } else { *stop_time.borrow_mut() = Local::now(); *imp2.running.lock().unwrap() = false; button.set_icon_name("media-playback-start-symbolic"); - this.set_watch_time("00:00:00"); + this.refresh_timer(); imp2.task_input.set_sensitive(true); this.save_task(*start_time.borrow(), *stop_time.borrow()); } @@ -240,7 +294,7 @@ impl FurtheranceWindow { fn setup_settings(&self) { let imp = imp::FurtheranceWindow::from_instance(self); - self.reset_vars(); + self.reset_idle(); // Enter starts timer let start = imp.start_button.clone(); @@ -267,7 +321,6 @@ impl FurtheranceWindow { Ok(val) => val, Err(_) => 1, }; - // If user was idle and has now returned... if idle_time < (settings_manager::get_int("idle-time") * 60) as u64 && *imp.idle_time_reached.lock().unwrap() @@ -309,7 +362,7 @@ impl FurtheranceWindow { gtk::DialogFlags::MODAL, gtk::MessageType::Warning, gtk::ButtonsType::None, - Some(&format!("<span size='x-large' weight='bold'>{}</span>", &gettext("Edit Task"))), + Some(&format!("<span size='x-large' weight='bold'>{}</span>", &gettext("Idle"))), ); dialog.add_buttons(&[ (&gettext("Discard"), gtk::ResponseType::Reject), @@ -317,9 +370,6 @@ impl FurtheranceWindow { ]); dialog.set_secondary_text(Some(&idle_time_msg)); - let app = FurtheranceApplication::default(); - app.system_notification(&idle_time_str, &question_str, dialog.clone()); - dialog.connect_response(clone!( @weak self as this, @strong dialog, @@ -328,16 +378,60 @@ impl FurtheranceWindow { this.set_subtract_idle(true); start_button.emit_clicked(); dialog.close(); - } else { - this.reset_vars(); + } else if resp == gtk::ResponseType::Accept { + this.reset_idle(); + dialog.close(); + } + })); + + *imp.idle_dialog.lock().unwrap() = dialog.clone(); + let app = FurtheranceApplication::default(); + app.system_idle_notification(&idle_time_str, &question_str); + + dialog.show(); + } + + fn pomodoro_over(&self, timer_start: DateTime<Local>, timer_stop: DateTime<Local>) { + let dialog = gtk::MessageDialog::with_markup( + Some(self), + gtk::DialogFlags::MODAL, + gtk::MessageType::Warning, + gtk::ButtonsType::None, + Some(&format!("<span size='x-large' weight='bold'>{}</span>", &gettext("Time's up!"))), + ); + dialog.add_buttons(&[ + (&gettext("Continue"), gtk::ResponseType::Accept), + (&gettext("Stop"), gtk::ResponseType::Reject) + ]); + + let app = FurtheranceApplication::default(); + app.system_pomodoro_notification(dialog.clone()); + dialog.connect_response(clone!( + @weak self as this, + @strong dialog => move |_, resp| { + let imp = imp::FurtheranceWindow::from_instance(&this); + if resp == gtk::ResponseType::Reject { + imp.start_button.set_icon_name("media-playback-start-symbolic"); + this.refresh_timer(); + imp.task_input.set_sensitive(true); + this.save_task(timer_start, timer_stop); + this.reset_idle(); + dialog.close(); + } else if resp == gtk::ResponseType::Accept { + *imp.pomodoro_continue.lock().unwrap() = true; + this.reset_idle(); + imp.start_button.emit_clicked(); dialog.close(); } })); - dialog.show() + let imp2 = imp::FurtheranceWindow::from_instance(self); + imp2.idle_dialog.lock().unwrap().close(); + + dialog.show(); } - pub fn reset_vars(&self) { + pub fn reset_idle(&self) { let imp = imp::FurtheranceWindow::from_instance(self); *imp.stored_idle.lock().unwrap() = 0; *imp.idle_notified.lock().unwrap() = false; @@ -365,6 +459,22 @@ impl FurtheranceWindow { self.display_toast(&gettext("Stop the timer to duplicate a task.")); } } + + pub fn refresh_timer (&self) { + let imp = imp::FurtheranceWindow::from_instance(self); + if settings_manager::get_bool("pomodoro") { + let mut mins = settings_manager::get_int("pomodoro-time"); + let mut hrs: i32 = 0; + if mins > 59 { + hrs = mins / 60; + mins = mins % 60; + } + let watch_text: &str = &format!("{:02}:{:02}:00", hrs, mins); + imp.watch.set_text(watch_text); + } else { + imp.watch.set_text("00:00:00"); + } + } } impl Default for FurtheranceWindow { |