diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8150df8..7883e91 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,15 @@ find_package(Qt6 REQUIRED COMPONENTS
qt_standard_project_setup()
-qt_add_executable(adhd main.cpp mainwindow.cpp)
+qt_add_executable(adhd
+ main.cpp
+ mainwindow.cpp
+ mainwindow.h
+ service.cpp
+ service.h
+ mainwindow.ui
+ sessionslistmodel.h sessionslistmodel.cpp
+)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user
new file mode 100644
index 0000000..2b8465e
--- /dev/null
+++ b/CMakeLists.txt.user
@@ -0,0 +1,460 @@
+
+
+
+
+
+ EnvironmentId
+ {1354daad-1138-41d2-985c-27357b7ee48a}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ true
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 0
+ 80
+ true
+ true
+ 1
+ 0
+ false
+ true
+ false
+ 2
+ true
+ true
+ 0
+ 8
+ true
+ false
+ 1
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ false
+ true
+ true
+ true
+ true
+
+ false
+
+
+ 0
+ true
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 5
+ true
+
+
+
+ true
+
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ Qt 6.8.6 for macOS
+ Qt 6.8.6 for macOS
+ qt.qt6.686.clang_64_kit
+ 0
+ 0
+ 0
+
+ Debug
+ 2
+ false
+
+ -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_BUILD_TYPE:STRING=Debug
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+ 0
+ /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Debug
+
+
+
+
+ all
+
+ false
+
+ true
+ Build
+ CMakeProjectManager.MakeStep
+
+ 1
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ Build
+ CMakeProjectManager.MakeStep
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Debug
+ CMakeProjectManager.CMakeBuildConfiguration
+
+
+ Release
+ 2
+ false
+
+ -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_BUILD_TYPE:STRING=Release
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+ /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Release
+
+
+
+
+ all
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Release
+ CMakeProjectManager.CMakeBuildConfiguration
+
+
+ RelWithDebInfo
+ 2
+ false
+
+ -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+ /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-RelWithDebInfo
+
+
+
+
+ all
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Release with Debug Information
+ CMakeProjectManager.CMakeBuildConfiguration
+
+
+ RelWithDebInfo
+ 2
+ false
+
+ -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+ 0
+ /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Profile
+
+
+
+
+ all
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Profile
+ CMakeProjectManager.CMakeBuildConfiguration
+
+
+ MinSizeRel
+ 2
+ false
+
+ -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_BUILD_TYPE:STRING=MinSizeRel
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+ /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-MinSizeRel
+
+
+
+
+ all
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Build
+ Build
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Clean
+ Clean
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Minimum Size Release
+ CMakeProjectManager.CMakeBuildConfiguration
+
+ 5
+
+
+ 0
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Install Application Manager package
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Deploy
+ Deploy
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ ProjectExplorer.CustomExecutableRunConfiguration
+
+ false
+ true
+ true
+
+ 1
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 1
+
+
+ ProjectExplorer.Project.Updater.FileVersion
+ 22
+
+
+ Version
+ 22
+
+
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..a442b66
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,45 @@
+## Todo
+- [ ] tab widget: one for settings, one for sessions, one for active session editing
+- [ ] active session view would show timer, pause, end, and edit session details
+- [ ] create new session button
+- [ ] click on a session to set as active
+
+- [ ] create separate overlay that the main app can listen to and notify of active session changes
+
+## Ideas
+- Show me all sessions and the current session
+- Sum up how many sessions I won/lost
+- A session can simply be described by simple paragraph of what you'll do, maybe markdown
+- Less rigidity in the sense of typing out thoughts and session goals...less due dates. Capture first, do later.
+- Later: allow notes within a session, jot things down, all live in the same folder
+ - It's like a todo.md
+- Every session is based on week # - year - session x
+- Every session is an individual document that lives on disk
+- See which week of the year that I'm in
+- See progress bar of how much of the year is done or percentage
+- Show stats on how many sessions total I've won/lost
+- Display a separate overlay window that shows me my active working item in center bottom in a pill
+- Can commit to github for "sync" and even include an input url to auto-upload the data directory to github
+
+- overlay can show the first line of the session doc (or separate input)
+- review of session can be: `Win`, `Neutral`, or `Loss`
+
+## Flow
+- select location to save all your session data in markdown files
+- data in the app parses the markdown file and uses it as the database, including the frontmatter
+
+1. At the start of the day/session, set an intention by creating a session
+2. Enter the top things to do this session in plain english (list or paragraph in markdown)
+3. Click start session, and clock runs. Can pause, or stop.
+4. When done for the session, select "Close session"...it will ask you to grade the session
+5. View previous sessions in a list
+
+## marketing
+- local-only
+- access files and metadata
+- simple video demo OR flowy research-site page that has simple visuals
+
+## Launch
+- [ ] install via brew
+- [ ] open source github
+- [ ] hackernews "for developers with adhd"...purist site
diff --git a/main.cpp b/main.cpp
index 1e87fcd..df55bee 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,6 +1,7 @@
#include
#include
#include "mainwindow.h"
+#include "service.h"
int main(int argc, char *argv[]) {
std::cout << "Starting adhd program" << std::endl;
diff --git a/mainwindow.cpp b/mainwindow.cpp
index bdf4f15..b694bda 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -1,13 +1,32 @@
#include "mainwindow.h"
+#include "service.h"
+#include "sessionslistmodel.h"
+#include
#include
+#include
+#include "ui_mainwindow.h"
-MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent), m_service(new Service(this))
{
- QWidget *centralWidget = new QWidget(this);
- QLabel *label = new QLabel(centralWidget);
- label->setText("Let's get some stuff done!");
+ ui = new Ui::MainWindow;
+ ui->setupUi(this);
- setCentralWidget(centralWidget);
+ m_listModel = new SessionsListModel(m_service, this);
+ ui->listView->setModel(m_listModel);
}
MainWindow::~MainWindow() {}
+
+void MainWindow::on_selectVaultButton_clicked()
+{
+ QString dir = QFileDialog::getExistingDirectory(this,
+ "Select Directory",
+ QDir::homePath(),
+ QFileDialog::ShowDirsOnly);
+ if (!dir.isEmpty()) {
+ m_service->setVaultDirectory(dir);
+ ui->tabWidget->setCurrentIndex(1);
+ ui->vaultLabel->setText(m_service->vaultDirectory());
+ }
+}
diff --git a/mainwindow.h b/mainwindow.h
index 2869038..e62dba7 100644
--- a/mainwindow.h
+++ b/mainwindow.h
@@ -4,15 +4,30 @@
#include
#include
+class Service;
+class SessionsListModel;
+
QT_BEGIN_NAMESPACE
QT_END_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
+
+ private slots:
+ void on_selectVaultButton_clicked();
+
+ private:
+ Ui::MainWindow *ui;
+ Service *m_service;
+ SessionsListModel *m_listModel;
};
#endif // MAINWINDOW_H
diff --git a/mainwindow.ui b/mainwindow.ui
new file mode 100644
index 0000000..0fd782c
--- /dev/null
+++ b/mainwindow.ui
@@ -0,0 +1,96 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 400
+ 600
+
+
+
+ MainWindow
+
+
+
+ -
+
+
+ 0
+
+
+
+ Settings
+
+
+
+
+ 140
+ 210
+ 88
+ 16
+
+
+
+ Select a folder
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+
+
+ 120
+ 240
+ 121
+ 32
+
+
+
+ Select vault
+
+
+
+
+
+ true
+
+
+
+
+
+ Sessions
+
+
+
-
+
+
+
+
+
+
+ Active
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/namegenerator.cpp b/namegenerator.cpp
new file mode 100644
index 0000000..8e307ad
--- /dev/null
+++ b/namegenerator.cpp
@@ -0,0 +1,9 @@
+#include "namegenerator.cpp"
+
+namespace {
+ constexpr char data[]
+}
+
+QString generate() {
+ return;
+}
diff --git a/namegenerator.h b/namegenerator.h
new file mode 100644
index 0000000..4aaa210
--- /dev/null
+++ b/namegenerator.h
@@ -0,0 +1,8 @@
+#ifndef NAMEGENERATOR_H
+#define NAMEGENERATOR_H
+
+#include
+
+QString generate();
+
+#endif //NAMEGENERATOR_H
diff --git a/service.cpp b/service.cpp
new file mode 100644
index 0000000..e3fd559
--- /dev/null
+++ b/service.cpp
@@ -0,0 +1,270 @@
+#include "service.h"
+#include
+#include
+#include
+#include
+
+bool Session::isComplete() const {
+ return result != UNSPECIFIED;
+}
+
+Service::Service(QObject *parent) : QObject(parent) {
+}
+
+Service::~Service() {}
+
+QString Service::vaultDirectory() const {
+ return m_vaultDirectory;
+}
+
+int Service::sessionCount() const {
+ return m_sessions.size();
+}
+
+int Service::completedSessionCount() const {
+ int count = 0;
+ for (const Session &session : m_sessions) {
+ if (session.isComplete()) {
+ count++;
+ }
+ }
+ return count;
+}
+
+int Service::totalWins() const {
+ int count = 0;
+ for (const Session &session : m_sessions) {
+ if (session.result == Session::WIN) {
+ count++;
+ }
+ }
+ return count;
+}
+
+int Service::totalLosses() const {
+ int count = 0;
+ for (const Session &session : m_sessions) {
+ if (session.result == Session::LOSS) {
+ count++;
+ }
+ }
+ return count;
+}
+
+int Service::totalNeutrals() const {
+ int count = 0;
+ for (const Session &session : m_sessions) {
+ if (session.result == Session::NEUTRAL) {
+ count++;
+ }
+ }
+ return count;
+}
+
+QList Service::sessions() const {
+ return m_sessions;
+}
+
+void Service::setVaultDirectory(const QString &path) {
+ m_sessions.clear();
+
+ qDebug() << "Setting vault directory to: " << path;
+
+ QDir dir = QDir(path);
+ if (!dir.exists()) {
+ qWarning() << "Vault directory does not exist";
+ return;
+ }
+
+ m_vaultDirectory = path;
+
+ dir.setFilter(QDir::Files);
+ dir.setSorting(QDir::Time);
+
+ QFileInfoList list = dir.entryInfoList();
+ for (int i = 0; i < list.size(); ++i) {
+ QFileInfo fileInfo = list.at(i);
+
+ if (fileInfo.completeSuffix() != QStringLiteral("md")) {
+ qWarning() << "Skipping invalid file format: " << fileInfo.completeSuffix();
+ continue;
+ }
+
+ qDebug() << "Parsing file: " << fileInfo.absoluteFilePath();
+ std::optional session = parseFile(fileInfo);
+ if (session) {
+ m_sessions.append(*session);
+ } else {
+ qWarning() << "Failed to parse session file: " << fileInfo.absoluteFilePath();
+ }
+ }
+
+ emit sessionsChanged();
+}
+
+Session Service::createSession(const QString &oneliner, const QString &content) {
+ assert(!m_vaultDirectory.isEmpty() && "Vault directory must be set before creating sessions");
+
+ QString uuid = QUuid().toString(QUuid::WithoutBraces);
+
+ Session session = {};
+ session.id = uuid;
+ session.oneliner = oneliner;
+ session.durationMs = 0;
+ session.content = content;
+ session.result = Session::UNSPECIFIED;
+ session.createdAt = QDateTime::currentDateTime();
+ session.lastModifiedAt = QDateTime::currentDateTime();
+
+ overwriteSessionFile(session);
+
+ m_sessions.prepend(session);
+ emit sessionsChanged();
+
+ return session;
+}
+
+void Service::updateSession(const Session &session) {
+ overwriteSessionFile(session);
+ for (int i = 0; i < m_sessions.size(); ++i) {
+ if (m_sessions[i].id == session.id) {
+ m_sessions[i] = session;
+ break;
+ }
+ }
+ emit sessionsChanged();
+}
+
+void Service::deleteSession(const QString &sessionId) {
+ QDir dir = QDir(m_vaultDirectory);
+ QString filePath = dir.filePath(sessionId + ".md");
+ QFile file = QFile(filePath);
+ if (!file.remove()) {
+ qWarning() << "Failed to delete session file: " << filePath;
+ return;
+ }
+
+ for (int i = 0; i < m_sessions.size(); ++i) {
+ if (m_sessions[i].id == sessionId) {
+ m_sessions.removeAt(i);
+ break;
+ }
+ }
+ emit sessionsChanged();
+}
+
+std::optional Service::parseFile(const QFileInfo &fileInfo) {
+ QString path = fileInfo.absoluteFilePath();
+ QFile file = QFile(path);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ return std::nullopt;
+ }
+
+ Session session = {};
+ session.id = file.fileName();
+ session.content = "";
+ session.oneliner = "";
+ session.durationMs = 0;
+ session.result = Session::UNSPECIFIED;
+ session.createdAt = fileInfo.birthTime();
+ session.lastModifiedAt = fileInfo.lastModified();
+
+ QTextStream in(&file);
+ int separatorCounts = 0;
+ while (!in.atEnd()) {
+ QString line = in.readLine();
+ // qDebug() << "Reading line: " << line;
+
+ if (separatorCounts >= 2) {
+ session.content += line + '\n';
+ continue;
+ }
+
+ switch (line[0].toLatin1()) {
+ case '-':
+ separatorCounts++;
+ continue;
+ break;
+ default:
+ break;
+ }
+ // parse each key value pair
+ QStringList parts = line.split(':');
+ if (parts.size() != 2) {
+ continue;
+ }
+ QString key = parts[0].trimmed();
+ QString value = parts[1].trimmed();
+ if (key == "Oneliner") {
+ session.oneliner = value;
+ continue;
+ } else if (key == "DurationMs") {
+ session.durationMs = value.toInt();
+ continue;
+ } else if (key == "Result") {
+ session.result = parseResult(value);
+ continue;
+ }
+ }
+
+ qDebug() << "Parsed oneliner: " << session.oneliner;
+ qDebug() << "Parsed durationMs: " << session.durationMs;
+ qDebug() << "Parsed result: " << static_cast(session.result);
+ qDebug() << "Parsed content: " << session.content;
+ qDebug() << "Created at: " << session.createdAt;
+ qDebug() << "Last modified at: " << session.lastModifiedAt;
+
+ file.close();
+
+ return session;
+}
+
+Session::Result Service::parseResult(const QString &resultStr) {
+ if (resultStr == "Win") {
+ return Session::WIN;
+ } else if (resultStr == "Loss") {
+ return Session::LOSS;
+ } else if (resultStr == "Neutral") {
+ return Session::NEUTRAL;
+ } else {
+ return Session::UNSPECIFIED;
+ }
+}
+
+void Service::overwriteSessionFile(const Session &session)
+{
+ // rewrites the entire file
+ QDir dir = QDir(m_vaultDirectory);
+ QString filePath = dir.filePath(session.id + ".md");
+ QFile file = QFile(filePath);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qWarning() << "Failed to update session file: " << filePath;
+ return;
+ }
+
+ QTextStream out(&file);
+ out << "---\n";
+ out << "Oneliner: " << session.oneliner << "\n";
+ out << "DurationMs: " << session.durationMs << "\n";
+ QString resultStr;
+ switch (session.result) {
+ case Session::WIN:
+ resultStr = "Win";
+ break;
+ case Session::LOSS:
+ resultStr = "Loss";
+ break;
+ case Session::NEUTRAL:
+ resultStr = "Neutral";
+ break;
+ default:
+ resultStr = "";
+ break;
+ }
+ if (!resultStr.isEmpty()) {
+ out << "Result: " << resultStr << "\n";
+ }
+ out << "---\n";
+ out << session.content << "\n";
+ file.close();
+}
diff --git a/service.h b/service.h
new file mode 100644
index 0000000..011bbad
--- /dev/null
+++ b/service.h
@@ -0,0 +1,82 @@
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct Session
+{
+ enum Result {
+ UNSPECIFIED = 0,
+ WIN,
+ LOSS,
+ NEUTRAL
+ };
+ // Becomes the file name
+ QString id;
+ // Stored in the frontmatter under 'Oneliner' key
+ QString oneliner;
+ // Stored in the frontmatter
+ int durationMs;
+ // After the --- onwards, serves as general scratchpad
+ QString content;
+ // Stored in the frontmatter under 'Result' key
+ Result result;
+
+ QDateTime lastModifiedAt;
+ QDateTime createdAt;
+
+ bool isComplete() const;
+};
+
+class Service : public QObject {
+ Q_OBJECT
+
+public:
+ explicit Service(QObject *parent = nullptr);
+ ~Service();
+
+ QList sessions() const;
+ QString vaultDirectory() const;
+
+ int sessionCount() const;
+ int completedSessionCount() const;
+ int totalWins() const;
+ int totalLosses() const;
+ int totalNeutrals() const;
+
+public slots:
+ /// @brief sets the directory from which to read/write sessions
+ /// Fails silently if not a valid path
+ /// Loads sessions from this path
+ void setVaultDirectory(const QString &path);
+
+ /// @brief creates the session and stores in sessions list
+ /// @returns the newly created session
+ Session createSession(const QString &oneliner, const QString &content);
+
+ /// @brief updates the session
+ /// Updates session in the sessions list, and the file associated
+ void updateSession(const Session &session);
+
+ void deleteSession(const QString &sessionId);
+
+signals:
+ void sessionsChanged();
+
+private:
+ QString m_vaultDirectory;
+ QList m_sessions;
+
+ /// @returns null Session if not a valid session markdown document
+ std::optional parseFile(const QFileInfo &fileInfo);
+
+ Session::Result parseResult(const QString &resultStr);
+ void overwriteSessionFile(const Session &session);
+};
+
+#endif //SERVICE_H
diff --git a/sessionslistmodel.cpp b/sessionslistmodel.cpp
new file mode 100644
index 0000000..77239b8
--- /dev/null
+++ b/sessionslistmodel.cpp
@@ -0,0 +1,34 @@
+#include "sessionslistmodel.h"
+
+SessionsListModel::SessionsListModel(Service *service, QObject *parent)
+ : QAbstractListModel{parent}, m_service(service)
+{
+ connect(m_service, &Service::sessionsChanged, this, [this]() {
+ beginResetModel();
+ endResetModel();
+ });
+}
+
+int SessionsListModel::rowCount(const QModelIndex &parent) const {
+ return m_service->sessions().size();
+}
+
+int SessionsListModel::columnCount(const QModelIndex &parent) const {
+ return 1;
+}
+
+QVariant SessionsListModel::data(const QModelIndex &index, int role) const {
+ if (index.row() < 0 || index.row() >= rowCount()) {
+ return QVariant();
+ } else if (!index.isValid()) {
+ return QVariant();
+ }
+
+ const Session session = m_service->sessions().at(index.row());
+ switch (role) {
+ case Qt::DisplayRole:
+ return session.oneliner;
+ default:
+ return QVariant();
+ }
+}
diff --git a/sessionslistmodel.h b/sessionslistmodel.h
new file mode 100644
index 0000000..1419bf4
--- /dev/null
+++ b/sessionslistmodel.h
@@ -0,0 +1,22 @@
+#ifndef SESSIONSLISTMODEL_H
+#define SESSIONSLISTMODEL_H
+
+#include
+#include
+#include "service.h"
+
+class SessionsListModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ explicit SessionsListModel(Service *service, QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const;
+
+private:
+ Service *m_service;
+};
+
+#endif // SESSIONSLISTMODEL_H