about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRicky Kresslein <rk@lakoliu.com>2022-04-28 19:18:15 +0300
committerRicky Kresslein <rk@lakoliu.com>2022-04-28 19:18:15 +0300
commit8ee461ce5c9cdd117b0e4b9f8dfec0a388d37f29 (patch)
tree43d3d25d80cc00f2855a3631faced64da1f440ab /src
parent794cf5b7025d72b9a2b61fc6f5df753363d6060a (diff)
downloadFurtherance-8ee461ce5c9cdd117b0e4b9f8dfec0a388d37f29.tar.zst
Add Pomodoro timer
Diffstat (limited to 'src')
-rwxr-xr-xsrc/application.rs58
-rwxr-xr-xsrc/gtk/preferences_window.ui37
-rwxr-xr-xsrc/ui/preferences_window.rs29
-rwxr-xr-xsrc/ui/window.rs178
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, &notification);
+        notification.set_priority(gio::NotificationPriority::High);
+
+        self.send_notification(Some("idle"), &notification);
+    }
+
+    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"), &notification);
     }
 }
 
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 {