Save configuration and allow custom script to execute when timer ends

This commit is contained in:
Vyn 2024-10-17 14:04:47 +02:00
parent 8585f5741f
commit da32449075
6 changed files with 24879 additions and 19 deletions

View file

@ -22,6 +22,7 @@ if (NOT Slint_FOUND)
endif (NOT Slint_FOUND) endif (NOT Slint_FOUND)
add_executable(focus src/main.cpp) add_executable(focus src/main.cpp)
target_include_directories(focus PRIVATE "external")
target_link_libraries(focus PRIVATE Slint::Slint) target_link_libraries(focus PRIVATE Slint::Slint)
slint_target_sources( slint_target_sources(
focus ui/app-window.slint focus ui/app-window.slint

View file

@ -16,6 +16,12 @@ Simple Pomodoro timer.
- Ninja (or Make but you will need to adapt the commands) - Ninja (or Make but you will need to adapt the commands)
- Git - Git
You can provide a custom script to execute when the timer ends, the usual usage is to display a
notification and play a sound, so if you don't provide your own script you also need:
- notify-send (notification's visual)
- gst-play-1.0 (notification's sound)
### Steps ### Steps
Fetch and setup the repository: Fetch and setup the repository:

24767
external/nlohmann/json.hpp vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,30 @@
#include "app-window.h" #include "app-window.h"
#include "slint_string.h"
#include "slint_timer.h" #include "slint_timer.h"
#include "slint.h" #include "slint.h"
#include "nlohmann/json.hpp"
#include <cstdlib> #include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <ostream>
#include <string>
class CountdownState { class CountdownState {
public: public:
CountdownState(slint::ComponentHandle<ui::AppWindow> ui) : ui_(ui) { CountdownState(slint::ComponentHandle<ui::AppWindow> ui) : ui_(ui) {
ui_->global<ui::State>().set_break_countdown_duration(60 * 5);
ui_->global<ui::State>().set_focus_countdown_duration(60 * 25);
ui_->global<ui::State>().set_max_session_count(4);
reset(); reset();
ui->on_start_stop([&]{
if (sessionStep() == ui::SessionStep::Setup || sessionStep() == ui::SessionStep::Finished) {
reset();
goNextStep();
start();
} else if (sessionStep() == ui::SessionStep::Focus || sessionStep() == ui::SessionStep::Break) {
toggleCountdown();
}
});
} }
ui::SessionStep sessionStep() const { ui::SessionStep sessionStep() const {
@ -105,13 +118,17 @@ class CountdownState {
countdown--; countdown--;
ui->global<ui::State>().set_countdown(countdown); ui->global<ui::State>().set_countdown(countdown);
if (countdown == 0) { if (countdown == 0) {
timer->stop(); timer->stop();
ui->global<ui::State>().set_countdown_status(ui::CountdownStatus::NotStarted); ui->global<ui::State>().set_countdown_status(ui::CountdownStatus::NotStarted);
// TODO make it customizable std::string timerEndingScript = std::string(ui->global<ui::State>().get_timer_ending_script());
system("notify-send -t 0 \"Pomodoro\" \"task\" &"); if (timerEndingScript.empty()) {
system("paplay /usr/share/sounds/freedesktop/stereo/complete.oga &"); timerEndingScript = "notify-send -t 0 \"Pomodoro\" \"timer ended!\" && "
"gst-play-1.0 /usr/share/sounds/freedesktop/stereo/complete.oga &";
}
system(timerEndingScript.c_str());
goNextStep(); goNextStep();
} }
} }
@ -122,24 +139,83 @@ class CountdownState {
slint::Timer timer_; slint::Timer timer_;
}; };
struct Config {
std::string timerEndingScript = "";
int focusDuration = 1500;
int breakDuration = 300;
int sessionCount = 4;
};
Config loadJsonConfig(const std::string& configPath) {
std::ifstream file(configPath);
Config config{};
if (!file) {
std::cerr << "Cant open config file: " << configPath << std::endl;
return config;
}
auto jsonConfig = nlohmann::json::parse(file);
file.close();
if (!jsonConfig.is_object()) {
std::cerr << "Config file malformated: " << configPath << std::endl;
}
if (jsonConfig["timerEndingScript"].is_string()) {
config.timerEndingScript = jsonConfig["timerEndingScript"].get<std::string>();
}
if (jsonConfig["focusDuration"].is_number_integer()) {
config.focusDuration = jsonConfig["focusDuration"].get<int>();
}
if (jsonConfig["breakDuration"].is_number_integer()) {
config.breakDuration = jsonConfig["breakDuration"].get<int>();
}
if (jsonConfig["sessionCount"].is_number_integer()) {
config.sessionCount = jsonConfig["sessionCount"].get<int>();
}
return config;
}
void saveJsonConfig(const std::string& configPath, const Config& config) {
std::ofstream file(configPath);
if (!file) {
std::cerr << "Cant open config file: " << configPath << std::endl;
return;
}
nlohmann::json jsonConfig;
jsonConfig["timerEndingScript"] = config.timerEndingScript;
jsonConfig["focusDuration"] = config.focusDuration;
jsonConfig["breakDuration"] = config.breakDuration;
jsonConfig["sessionCount"] = config.sessionCount;
file << jsonConfig.dump(4);
file.close();
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
std::filesystem::create_directory(std::string(getenv("HOME")) + "/.config/focus");
const auto configPath = std::string(getenv("HOME")) + "/.config/focus/config.json";
slint::ComponentHandle<ui::AppWindow> ui = ui::AppWindow::create(); slint::ComponentHandle<ui::AppWindow> ui = ui::AppWindow::create();
Config config = loadJsonConfig(configPath);
ui->global<ui::State>().set_timer_ending_script(slint::SharedString(config.timerEndingScript));
ui->global<ui::State>().set_focus_countdown_duration(config.focusDuration);
ui->global<ui::State>().set_break_countdown_duration(config.breakDuration);
ui->global<ui::State>().set_max_session_count(config.sessionCount);
CountdownState countdown(ui); CountdownState countdown(ui);
ui->on_start_stop([&]{
if (countdown.sessionStep() == ui::SessionStep::Setup || countdown.sessionStep() == ui::SessionStep::Finished) {
countdown.reset();
countdown.goNextStep();
countdown.start();
} else if (countdown.sessionStep() == ui::SessionStep::Focus || countdown.sessionStep() == ui::SessionStep::Break) {
countdown.toggleCountdown();
}
});
ui->global<ui::State>().on_config_changed([&] { ui->global<ui::State>().on_config_changed([&] {
countdown.reset(); countdown.reset();
config.timerEndingScript = ui->global<ui::State>().get_timer_ending_script();
config.focusDuration = ui->global<ui::State>().get_focus_countdown_duration();
config.breakDuration = ui->global<ui::State>().get_break_countdown_duration();
config.sessionCount = ui->global<ui::State>().get_max_session_count();
saveJsonConfig(configPath, config);
}); });
ui->run(); ui->run();

View file

@ -1,6 +1,7 @@
import { State, SessionStep, CountdownStatus } from "./state.slint"; import { State, SessionStep, CountdownStatus } from "./state.slint";
import { VText, VButton, VSlider, Palette } from "@selenite"; import { VText, VButton, VSlider, VTextInput, Palette } from "@selenite";
import { Utils } from "utils.slint"; import { Utils } from "utils.slint";
import { ComboBox } from "std-widgets.slint";
export component SettingsView inherits Rectangle { export component SettingsView inherits Rectangle {
VerticalLayout { VerticalLayout {
@ -40,5 +41,13 @@ export component SettingsView inherits Rectangle {
format-value(value) => { return Math.round(value); } format-value(value) => { return Math.round(value); }
released(value) => { State.max-session-count = Math.round(value); } released(value) => { State.max-session-count = Math.round(value); }
} }
VTextInput {
label: "Timer ending script";
label-size: 1.25rem;
label-alignment: TextHorizontalAlignment.center;
placeholder: "Default";
text <=> State.timer-ending-script;
}
} }
} }

View file

@ -19,6 +19,7 @@ export global State {
in-out property <CountdownStatus> countdown-status; in-out property <CountdownStatus> countdown-status;
in-out property <int> current-session; in-out property <int> current-session;
in-out property <int> max-session-count; in-out property <int> max-session-count;
in-out property <string> timer-ending-script;
callback config-changed(); callback config-changed();
} }