From 9e5f0a5d2b6d3ca04c10f277a572882c51e80251 Mon Sep 17 00:00:00 2001 From: talksik Date: Sun, 1 Feb 2026 13:21:21 -0800 Subject: [PATCH] generate ui core flows --- CMakeLists.txt | 16 ++- createsessiondialog.cpp | 40 ++++++++ createsessiondialog.h | 28 +++++ createsessiondialog.ui | 96 +++++++++++++++++ endsessiondialog.cpp | 68 +++++++++++++ endsessiondialog.h | 32 ++++++ endsessiondialog.ui | 221 ++++++++++++++++++++++++++++++++++++++++ focusoverlay.cpp | 71 +++++++++++++ focusoverlay.h | 34 +++++++ focusoverlay.ui | 114 +++++++++++++++++++++ mainwindow.cpp | 220 ++++++++++++++++++++++++++++++++++++++- mainwindow.h | 27 ++++- mainwindow.ui | 193 +++++++++++++++++++++++++++++++++++ namegenerator.cpp | 8 +- service.cpp | 83 ++++++++++++++- service.h | 22 ++++ sessionitemdelegate.cpp | 139 +++++++++++++++++++++++++ sessionitemdelegate.h | 20 ++++ sessionslistmodel.cpp | 11 ++ sessionslistmodel.h | 4 +- 20 files changed, 1433 insertions(+), 14 deletions(-) create mode 100644 createsessiondialog.cpp create mode 100644 createsessiondialog.h create mode 100644 createsessiondialog.ui create mode 100644 endsessiondialog.cpp create mode 100644 endsessiondialog.h create mode 100644 endsessiondialog.ui create mode 100644 focusoverlay.cpp create mode 100644 focusoverlay.h create mode 100644 focusoverlay.ui create mode 100644 sessionitemdelegate.cpp create mode 100644 sessionitemdelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7883e91..f7890ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,21 @@ qt_add_executable(adhd service.cpp service.h mainwindow.ui - sessionslistmodel.h sessionslistmodel.cpp + sessionslistmodel.h + sessionslistmodel.cpp + sessionitemdelegate.h + sessionitemdelegate.cpp + createsessiondialog.h + createsessiondialog.cpp + createsessiondialog.ui + focusoverlay.h + focusoverlay.cpp + focusoverlay.ui + endsessiondialog.h + endsessiondialog.cpp + endsessiondialog.ui + namegenerator.h + namegenerator.cpp ) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. diff --git a/createsessiondialog.cpp b/createsessiondialog.cpp new file mode 100644 index 0000000..db05854 --- /dev/null +++ b/createsessiondialog.cpp @@ -0,0 +1,40 @@ +#include "createsessiondialog.h" +#include "ui_createsessiondialog.h" +#include + +CreateSessionDialog::CreateSessionDialog(QWidget *parent) + : QDialog(parent), + ui(new Ui::CreateSessionDialog) +{ + ui->setupUi(this); + + setWindowTitle("Create New Session"); + + // Disable OK button initially + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + // Connect oneliner field to validation + connect(ui->onelinerEdit, &QLineEdit::textChanged, + this, &CreateSessionDialog::onOnelinerChanged); +} + +CreateSessionDialog::~CreateSessionDialog() +{ + delete ui; +} + +QString CreateSessionDialog::getOneliner() const +{ + return ui->onelinerEdit->text().trimmed(); +} + +QString CreateSessionDialog::getContent() const +{ + return ui->contentEdit->toPlainText().trimmed(); +} + +void CreateSessionDialog::onOnelinerChanged(const QString &text) +{ + bool isValid = !text.trimmed().isEmpty(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid); +} diff --git a/createsessiondialog.h b/createsessiondialog.h new file mode 100644 index 0000000..fe07bf8 --- /dev/null +++ b/createsessiondialog.h @@ -0,0 +1,28 @@ +#ifndef CREATESESSIONDIALOG_H +#define CREATESESSIONDIALOG_H + +#include + +namespace Ui { +class CreateSessionDialog; +} + +class CreateSessionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateSessionDialog(QWidget *parent = nullptr); + ~CreateSessionDialog(); + + QString getOneliner() const; + QString getContent() const; + +private slots: + void onOnelinerChanged(const QString &text); + +private: + Ui::CreateSessionDialog *ui; +}; + +#endif // CREATESESSIONDIALOG_H diff --git a/createsessiondialog.ui b/createsessiondialog.ui new file mode 100644 index 0000000..dea1ee9 --- /dev/null +++ b/createsessiondialog.ui @@ -0,0 +1,96 @@ + + + CreateSessionDialog + + + + 0 + 0 + 500 + 300 + + + + Create Session + + + + + + + + Oneliner: + + + + + + + What will you focus on? + + + + + + + Initial Content: + + + + + + + Optional: Add initial notes or context... + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CreateSessionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateSessionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/endsessiondialog.cpp b/endsessiondialog.cpp new file mode 100644 index 0000000..8efc133 --- /dev/null +++ b/endsessiondialog.cpp @@ -0,0 +1,68 @@ +#include "endsessiondialog.h" +#include "ui_endsessiondialog.h" + +EndSessionDialog::EndSessionDialog(const QString &oneliner, int elapsedMs, QWidget *parent) + : QDialog(parent), + ui(new Ui::EndSessionDialog), + m_selectedResult(Session::UNSPECIFIED) +{ + ui->setupUi(this); + + setWindowTitle("End Session"); + + // Set oneliner and elapsed time + ui->onelinerLabel->setText(oneliner); + + int seconds = elapsedMs / 1000; + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + int secs = seconds % 60; + + QString timeStr = QString("%1:%2:%3") + .arg(hours, 2, 10, QChar('0')) + .arg(minutes, 2, 10, QChar('0')) + .arg(secs, 2, 10, QChar('0')); + + ui->elapsedLabel->setText("Duration: " + timeStr); + + // Connect buttons + connect(ui->winButton, &QPushButton::clicked, + this, &EndSessionDialog::onWinClicked); + connect(ui->lossButton, &QPushButton::clicked, + this, &EndSessionDialog::onLossClicked); + connect(ui->neutralButton, &QPushButton::clicked, + this, &EndSessionDialog::onNeutralClicked); +} + +EndSessionDialog::~EndSessionDialog() +{ + delete ui; +} + +Session::Result EndSessionDialog::getResult() const +{ + return m_selectedResult; +} + +QString EndSessionDialog::getAdditionalNotes() const +{ + return ui->notesEdit->toPlainText().trimmed(); +} + +void EndSessionDialog::onWinClicked() +{ + m_selectedResult = Session::WIN; + accept(); +} + +void EndSessionDialog::onLossClicked() +{ + m_selectedResult = Session::LOSS; + accept(); +} + +void EndSessionDialog::onNeutralClicked() +{ + m_selectedResult = Session::NEUTRAL; + accept(); +} diff --git a/endsessiondialog.h b/endsessiondialog.h new file mode 100644 index 0000000..0f55c5e --- /dev/null +++ b/endsessiondialog.h @@ -0,0 +1,32 @@ +#ifndef ENDSESSIONDIALOG_H +#define ENDSESSIONDIALOG_H + +#include +#include "service.h" + +namespace Ui { +class EndSessionDialog; +} + +class EndSessionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit EndSessionDialog(const QString &oneliner, int elapsedMs, QWidget *parent = nullptr); + ~EndSessionDialog(); + + Session::Result getResult() const; + QString getAdditionalNotes() const; + +private slots: + void onWinClicked(); + void onLossClicked(); + void onNeutralClicked(); + +private: + Ui::EndSessionDialog *ui; + Session::Result m_selectedResult; +}; + +#endif // ENDSESSIONDIALOG_H diff --git a/endsessiondialog.ui b/endsessiondialog.ui new file mode 100644 index 0000000..bd84603 --- /dev/null +++ b/endsessiondialog.ui @@ -0,0 +1,221 @@ + + + EndSessionDialog + + + + 0 + 0 + 500 + 400 + + + + End Session + + + + + + + 16 + 75 + true + + + + How did your session go? + + + Qt::AlignCenter + + + + + + + + 14 + + + + Session Oneliner + + + Qt::AlignCenter + + + true + + + + + + + Duration: 00:00:00 + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + 0 + 60 + + + + + 14 + 75 + true + + + + QPushButton { + background-color: #2ecc71; + color: white; + border-radius: 5px; +} +QPushButton:hover { + background-color: #27ae60; +} +QPushButton:pressed { + background-color: #229954; +} + + + WIN + + + + + + + + 0 + 60 + + + + + 14 + 75 + true + + + + QPushButton { + background-color: #e74c3c; + color: white; + border-radius: 5px; +} +QPushButton:hover { + background-color: #c0392b; +} +QPushButton:pressed { + background-color: #a93226; +} + + + LOSS + + + + + + + + 0 + 60 + + + + + 14 + 75 + true + + + + QPushButton { + background-color: #95a5a6; + color: white; + border-radius: 5px; +} +QPushButton:hover { + background-color: #7f8c8d; +} +QPushButton:pressed { + background-color: #707b7c; +} + + + NEUTRAL + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Optional: Add final notes + + + + + + + + 16777215 + 100 + + + + Any final thoughts or notes about this session... + + + + + + + + diff --git a/focusoverlay.cpp b/focusoverlay.cpp new file mode 100644 index 0000000..cd2fa62 --- /dev/null +++ b/focusoverlay.cpp @@ -0,0 +1,71 @@ +#include "focusoverlay.h" +#include "ui_focusoverlay.h" +#include +#include + +FocusOverlay::FocusOverlay(QWidget *parent) + : QWidget(parent), + ui(new Ui::FocusOverlay) +{ + ui->setupUi(this); + + // Set window flags for always-on-top frameless window + setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); + setAttribute(Qt::WA_TranslucentBackground); + + // Connect buttons + connect(ui->resetButton, &QPushButton::clicked, + this, &FocusOverlay::onResetButtonClicked); + connect(ui->endButton, &QPushButton::clicked, + this, &FocusOverlay::onEndButtonClicked); + + // Position window + positionAtBottomCenter(); +} + +FocusOverlay::~FocusOverlay() +{ + delete ui; +} + +void FocusOverlay::setOneliner(const QString &oneliner) +{ + ui->onelinerLabel->setText(oneliner); +} + +void FocusOverlay::setElapsedTime(int elapsedMs) +{ + int seconds = elapsedMs / 1000; + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + int secs = seconds % 60; + + QString timeStr = QString("%1:%2:%3") + .arg(hours, 2, 10, QChar('0')) + .arg(minutes, 2, 10, QChar('0')) + .arg(secs, 2, 10, QChar('0')); + + ui->elapsedLabel->setText("Elapsed: " + timeStr); +} + +void FocusOverlay::onResetButtonClicked() +{ + emit resetClicked(); +} + +void FocusOverlay::onEndButtonClicked() +{ + emit endSessionClicked(); +} + +void FocusOverlay::positionAtBottomCenter() +{ + QScreen *screen = QGuiApplication::primaryScreen(); + if (!screen) return; + + QRect screenGeometry = screen->geometry(); + int x = (screenGeometry.width() - width()) / 2; + int y = screenGeometry.height() - height() - 300; // 300px from bottom + + move(x, y); +} diff --git a/focusoverlay.h b/focusoverlay.h new file mode 100644 index 0000000..58862ca --- /dev/null +++ b/focusoverlay.h @@ -0,0 +1,34 @@ +#ifndef FOCUSOVERLAY_H +#define FOCUSOVERLAY_H + +#include + +namespace Ui { +class FocusOverlay; +} + +class FocusOverlay : public QWidget +{ + Q_OBJECT + +public: + explicit FocusOverlay(QWidget *parent = nullptr); + ~FocusOverlay(); + + void setOneliner(const QString &oneliner); + void setElapsedTime(int elapsedMs); + +signals: + void resetClicked(); + void endSessionClicked(); + +private slots: + void onResetButtonClicked(); + void onEndButtonClicked(); + +private: + Ui::FocusOverlay *ui; + void positionAtBottomCenter(); +}; + +#endif // FOCUSOVERLAY_H diff --git a/focusoverlay.ui b/focusoverlay.ui new file mode 100644 index 0000000..c7cc5e8 --- /dev/null +++ b/focusoverlay.ui @@ -0,0 +1,114 @@ + + + FocusOverlay + + + + 0 + 0 + 400 + 120 + + + + Focus Session + + + QWidget#FocusOverlay { + background-color: rgba(50, 50, 50, 200); + border-radius: 10px; + border: 2px solid rgba(100, 100, 100, 255); +} + +QLabel { + color: white; +} + +QPushButton { + background-color: rgba(80, 80, 80, 255); + color: white; + border: 1px solid rgba(120, 120, 120, 255); + border-radius: 5px; + padding: 5px 10px; + min-width: 60px; +} + +QPushButton:hover { + background-color: rgba(100, 100, 100, 255); +} + +QPushButton:pressed { + background-color: rgba(60, 60, 60, 255); +} + + + + 10 + + + 15 + + + 15 + + + 15 + + + 15 + + + + + + 14 + 75 + true + + + + Session Oneliner + + + Qt::AlignCenter + + + + + + + + 12 + + + + Elapsed: 00:00:00 + + + Qt::AlignCenter + + + + + + + + + Reset + + + + + + + End Session + + + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index b694bda..541bab2 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,19 +1,51 @@ #include "mainwindow.h" #include "service.h" #include "sessionslistmodel.h" +#include "sessionitemdelegate.h" +#include "createsessiondialog.h" +#include "focusoverlay.h" +#include "endsessiondialog.h" #include #include #include +#include +#include #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), m_service(new Service(this)) + : QMainWindow(parent), + m_service(new Service(this)), + m_focusOverlay(nullptr), + m_updatingActiveFields(false) { ui = new Ui::MainWindow; ui->setupUi(this); m_listModel = new SessionsListModel(m_service, this); ui->listView->setModel(m_listModel); + ui->listView->setItemDelegate(new SessionItemDelegate(this)); + + // Connect Service signals + connect(m_service, &Service::activeSessionChanged, + this, &MainWindow::onActiveSessionChanged); + connect(m_service, &Service::sessionTimerTicked, + this, &MainWindow::onSessionTimerTicked); + + // Connect Active tab field changes + connect(ui->oneLineEdit, &QLineEdit::editingFinished, + this, &MainWindow::onActiveOnelinerChanged); + + // Debounce content text changes + QTimer *contentDebounce = new QTimer(this); + contentDebounce->setSingleShot(true); + contentDebounce->setInterval(500); + connect(ui->contentTextEdit, &QTextEdit::textChanged, + [contentDebounce]() { contentDebounce->start(); }); + connect(contentDebounce, &QTimer::timeout, + this, &MainWindow::onActiveContentChanged); + + // Set initial state: no active session + ui->activeStackedWidget->setCurrentIndex(0); } MainWindow::~MainWindow() {} @@ -27,6 +59,190 @@ void MainWindow::on_selectVaultButton_clicked() if (!dir.isEmpty()) { m_service->setVaultDirectory(dir); ui->tabWidget->setCurrentIndex(1); - ui->vaultLabel->setText(m_service->vaultDirectory()); + ui->vaultLabel->setText(dir); } } + +void MainWindow::on_newSessionButton_clicked() +{ + CreateSessionDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + QString oneliner = dialog.getOneliner(); + QString content = dialog.getContent(); + + Session session = m_service->createSession(oneliner, content); + m_service->startSession(session); + } +} + +void MainWindow::on_startSessionButton_clicked() +{ + QModelIndex index = ui->listView->currentIndex(); + if (!index.isValid()) { + QMessageBox::warning(this, "No Selection", "Please select a session to start."); + return; + } + + Session session = m_listModel->getSessionByIndex(index); + m_service->startSession(session); +} + +void MainWindow::on_endSessionButton_clicked() +{ + auto activeSession = m_service->activeSession(); + if (!activeSession) { + return; + } + + EndSessionDialog dialog(activeSession->oneliner, m_service->getElapsedMs(), this); + if (dialog.exec() == QDialog::Accepted) { + Session::Result result = dialog.getResult(); + QString notes = dialog.getAdditionalNotes(); + + // Append notes to content if provided + if (!notes.isEmpty()) { + Session updated = *activeSession; + if (!updated.content.isEmpty()) { + updated.content += "\n\n"; + } + updated.content += notes; + m_service->updateSession(updated); + } + + m_service->endSession(result); + } +} + +void MainWindow::onActiveSessionChanged() +{ + auto activeSession = m_service->activeSession(); + + if (activeSession) { + // Session started + ui->activeStackedWidget->setCurrentIndex(1); + updateActiveTab(); + + // Create and show overlay + if (!m_focusOverlay) { + m_focusOverlay = new FocusOverlay(); + connect(m_focusOverlay, &FocusOverlay::resetClicked, + this, &MainWindow::onOverlayResetClicked); + connect(m_focusOverlay, &FocusOverlay::endSessionClicked, + this, &MainWindow::onOverlayEndSessionClicked); + } + + m_focusOverlay->setOneliner(activeSession->oneliner); + m_focusOverlay->setElapsedTime(0); + m_focusOverlay->show(); + + // Switch to Active tab + ui->tabWidget->setCurrentIndex(2); + } else { + // Session ended + ui->activeStackedWidget->setCurrentIndex(0); + + // Hide and delete overlay + if (m_focusOverlay) { + m_focusOverlay->hide(); + m_focusOverlay->deleteLater(); + m_focusOverlay = nullptr; + } + } +} + +void MainWindow::onSessionTimerTicked(int elapsedMs) +{ + ui->elapsedLabel->setText(formatDuration(elapsedMs)); + + if (m_focusOverlay) { + m_focusOverlay->setElapsedTime(elapsedMs); + } +} + +void MainWindow::onActiveOnelinerChanged() +{ + if (m_updatingActiveFields) return; + + auto activeSession = m_service->activeSession(); + if (!activeSession) return; + + Session updated = *activeSession; + updated.oneliner = ui->oneLineEdit->text(); + updated.lastModifiedAt = QDateTime::currentDateTime(); + + m_updatingActiveFields = true; + m_service->updateSession(updated); + m_service->updateActiveSession(updated); + + // Update overlay + if (m_focusOverlay) { + m_focusOverlay->setOneliner(updated.oneliner); + } + + m_updatingActiveFields = false; +} + +void MainWindow::onActiveContentChanged() +{ + if (m_updatingActiveFields) return; + + auto activeSession = m_service->activeSession(); + if (!activeSession) return; + + Session updated = *activeSession; + updated.content = ui->contentTextEdit->toPlainText(); + updated.lastModifiedAt = QDateTime::currentDateTime(); + + m_updatingActiveFields = true; + m_service->updateSession(updated); + m_service->updateActiveSession(updated); + m_updatingActiveFields = false; +} + +void MainWindow::onOverlayResetClicked() +{ + QMessageBox::StandardButton reply = QMessageBox::question( + this, + "Reset Timer", + "Are you sure you want to reset the timer to 00:00:00?", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + m_service->resetSessionTimer(); + } +} + +void MainWindow::onOverlayEndSessionClicked() +{ + on_endSessionButton_clicked(); +} + +void MainWindow::updateActiveTab() +{ + auto activeSession = m_service->activeSession(); + if (!activeSession) return; + + m_updatingActiveFields = true; + + ui->oneLineEdit->setText(activeSession->oneliner); + ui->elapsedLabel->setText(formatDuration(m_service->getElapsedMs())); + + ui->createdLabel->setText(activeSession->createdAt.toString("yyyy-MM-dd hh:mm:ss")); + ui->modifiedLabel->setText(activeSession->lastModifiedAt.toString("yyyy-MM-dd hh:mm:ss")); + ui->contentTextEdit->setPlainText(activeSession->content); + + m_updatingActiveFields = false; +} + +QString MainWindow::formatDuration(int ms) const +{ + int seconds = ms / 1000; + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + int secs = seconds % 60; + + return QString("%1:%2:%3") + .arg(hours, 2, 10, QChar('0')) + .arg(minutes, 2, 10, QChar('0')) + .arg(secs, 2, 10, QChar('0')); +} diff --git a/mainwindow.h b/mainwindow.h index e62dba7..9030128 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -6,6 +6,8 @@ class Service; class SessionsListModel; +class FocusOverlay; +class QTimer; QT_BEGIN_NAMESPACE QT_END_NAMESPACE @@ -21,13 +23,34 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); - private slots: +private slots: void on_selectVaultButton_clicked(); + void on_newSessionButton_clicked(); + void on_startSessionButton_clicked(); + void on_endSessionButton_clicked(); - private: + // Service signal handlers + void onActiveSessionChanged(); + void onSessionTimerTicked(int elapsedMs); + + // Active tab field editing + void onActiveOnelinerChanged(); + void onActiveContentChanged(); + + // Overlay handlers + void onOverlayResetClicked(); + void onOverlayEndSessionClicked(); + +private: Ui::MainWindow *ui; Service *m_service; SessionsListModel *m_listModel; + FocusOverlay *m_focusOverlay; + + bool m_updatingActiveFields; + + void updateActiveTab(); + QString formatDuration(int ms) const; }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 0fd782c..7dbef64 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -65,6 +65,37 @@ Sessions + + + + + + New Session + + + + + + + Start Selected + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -74,6 +105,168 @@ Active + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + No session running. Start a session to begin. + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + true + + + + + 0 + 0 + 358 + 457 + + + + + + + + + Oneliner: + + + + + + + + + + Elapsed Time: + + + + + + + 00:00:00 + + + + + + + Created: + + + + + + + - + + + + + + + Modified: + + + + + + + - + + + + + + + Content: + + + + + + + Add notes, context, or anything related to this session... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + End Session + + + + + + + + + + + + diff --git a/namegenerator.cpp b/namegenerator.cpp index 8e307ad..93886d3 100644 --- a/namegenerator.cpp +++ b/namegenerator.cpp @@ -1,9 +1,5 @@ -#include "namegenerator.cpp" - -namespace { - constexpr char data[] -} +#include "namegenerator.h" QString generate() { - return; + return QString("generated-name-placeholder"); } diff --git a/service.cpp b/service.cpp index e3fd559..2f21323 100644 --- a/service.cpp +++ b/service.cpp @@ -8,7 +8,12 @@ bool Session::isComplete() const { return result != UNSPECIFIED; } -Service::Service(QObject *parent) : QObject(parent) { +Service::Service(QObject *parent) + : QObject(parent), + m_tickTimer(new QTimer(this)) +{ + m_tickTimer->setInterval(1000); // Fire every second + connect(m_tickTimer, &QTimer::timeout, this, &Service::onTickTimerFired); } Service::~Service() {} @@ -161,7 +166,7 @@ std::optional Service::parseFile(const QFileInfo &fileInfo) { } Session session = {}; - session.id = file.fileName(); + session.id = fileInfo.baseName(); // Get filename without extension session.content = ""; session.oneliner = ""; session.durationMs = 0; @@ -268,3 +273,77 @@ void Service::overwriteSessionFile(const Session &session) out << session.content << "\n"; file.close(); } + +// Session lifecycle methods + +void Service::startSession(const Session &session) { + if (m_activeSession.has_value()) { + qWarning() << "A session is already active. End it before starting a new one."; + return; + } + + m_activeSession = session; + m_sessionTimer.start(); + m_tickTimer->start(); + + emit activeSessionChanged(); + emit sessionTimerTicked(0); +} + +void Service::updateActiveSession(const Session &session) { + if (!m_activeSession.has_value()) { + qWarning() << "No active session to update."; + return; + } + + m_activeSession = session; + // Don't emit activeSessionChanged - just update the session data + // Timer keeps running +} + +int Service::getElapsedMs() const { + if (!m_activeSession) { + return 0; + } + return m_sessionTimer.elapsed(); +} + +void Service::resetSessionTimer() { + if (!m_activeSession) { + return; + } + + m_sessionTimer.restart(); + emit sessionTimerTicked(0); +} + +void Service::endSession(Session::Result result) { + if (!m_activeSession) { + return; + } + + // Update session with final values + Session updated = *m_activeSession; + updated.durationMs = getElapsedMs(); + updated.result = result; + updated.lastModifiedAt = QDateTime::currentDateTime(); + + // Save to file and update in list + updateSession(updated); + + // Clear active state + m_activeSession.reset(); + m_tickTimer->stop(); + + emit activeSessionChanged(); +} + +std::optional Service::activeSession() const { + return m_activeSession; +} + +void Service::onTickTimerFired() { + if (m_activeSession) { + emit sessionTimerTicked(getElapsedMs()); + } +} diff --git a/service.h b/service.h index 011bbad..393eb55 100644 --- a/service.h +++ b/service.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include struct Session @@ -65,18 +67,38 @@ public slots: void deleteSession(const QString &sessionId); + // Session lifecycle methods + void startSession(const Session &session); + void updateActiveSession(const Session &session); // Update without restarting timer + int getElapsedMs() const; + void resetSessionTimer(); + void endSession(Session::Result result); + std::optional activeSession() const; + signals: void sessionsChanged(); + void activeSessionChanged(); + void sessionTimerTicked(int elapsedMs); private: QString m_vaultDirectory; QList m_sessions; + // Active session tracking + std::optional m_activeSession; + QElapsedTimer m_sessionTimer; + QTimer *m_tickTimer; + /// @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); + +private slots: + void onTickTimerFired(); }; +Q_DECLARE_METATYPE(Session) + #endif //SERVICE_H diff --git a/sessionitemdelegate.cpp b/sessionitemdelegate.cpp new file mode 100644 index 0000000..183f543 --- /dev/null +++ b/sessionitemdelegate.cpp @@ -0,0 +1,139 @@ +#include "sessionitemdelegate.h" +#include "service.h" +#include +#include + +SessionItemDelegate::SessionItemDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +void SessionItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!index.isValid()) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + + painter->save(); + + // Get session data + Session session = index.data(Qt::UserRole).value(); + QString oneliner = session.oneliner; + + // Format duration + int seconds = session.durationMs / 1000; + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + int secs = seconds % 60; + QString durationStr = QString("%1:%2:%3") + .arg(hours, 2, 10, QChar('0')) + .arg(minutes, 2, 10, QChar('0')) + .arg(secs, 2, 10, QChar('0')); + + // Format result + 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 = "UNSPECIFIED"; + break; + } + + // Format relative time + QDateTime now = QDateTime::currentDateTime(); + qint64 secsSinceModified = session.lastModifiedAt.secsTo(now); + QString relativeTime; + if (secsSinceModified < 60) { + relativeTime = "just now"; + } else if (secsSinceModified < 3600) { + relativeTime = QString("%1 min ago").arg(secsSinceModified / 60); + } else if (secsSinceModified < 86400) { + relativeTime = QString("%1 hr ago").arg(secsSinceModified / 3600); + } else { + relativeTime = QString("%1 days ago").arg(secsSinceModified / 86400); + } + + QString metadata = QString("Duration: %1 | Result: %2 | Modified: %3") + .arg(durationStr) + .arg(resultStr) + .arg(relativeTime); + + // Draw background + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } else { + painter->fillRect(option.rect, option.palette.base()); + } + + // Draw result indicator bar on the left + QColor indicatorColor; + switch (session.result) { + case Session::WIN: + indicatorColor = QColor(46, 204, 113); // Green + break; + case Session::LOSS: + indicatorColor = QColor(231, 76, 60); // Red + break; + case Session::NEUTRAL: + indicatorColor = QColor(149, 165, 166); // Gray + break; + default: + indicatorColor = QColor(0, 0, 0, 0); // Transparent + break; + } + + if (session.result != Session::UNSPECIFIED) { + QRect indicatorRect(option.rect.left(), option.rect.top(), 4, option.rect.height()); + painter->fillRect(indicatorRect, indicatorColor); + } + + // Set up text drawing + QRect textRect = option.rect.adjusted(10, 5, -5, -5); + QColor textColor = (option.state & QStyle::State_Selected) + ? option.palette.highlightedText().color() + : option.palette.text().color(); + + // Draw oneliner (bold, larger font) + painter->setPen(textColor); + QFont onelineFont = option.font; + onelineFont.setPointSize(14); + onelineFont.setBold(true); + painter->setFont(onelineFont); + + QRect onelineRect = textRect; + onelineRect.setHeight(24); + QString elidedOneliner = painter->fontMetrics().elidedText(oneliner, Qt::ElideRight, onelineRect.width()); + painter->drawText(onelineRect, Qt::AlignLeft | Qt::AlignVCenter, elidedOneliner); + + // Draw metadata (smaller font, lighter color) + QFont metadataFont = option.font; + metadataFont.setPointSize(11); + painter->setFont(metadataFont); + + QColor metadataColor = textColor; + metadataColor.setAlpha(180); // Make it slightly transparent + painter->setPen(metadataColor); + + QRect metadataRect = textRect; + metadataRect.setTop(onelineRect.bottom() + 2); + QString elidedMetadata = painter->fontMetrics().elidedText(metadata, Qt::ElideRight, metadataRect.width()); + painter->drawText(metadataRect, Qt::AlignLeft | Qt::AlignVCenter, elidedMetadata); + + painter->restore(); +} + +QSize SessionItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + return QSize(option.rect.width(), 65); +} diff --git a/sessionitemdelegate.h b/sessionitemdelegate.h new file mode 100644 index 0000000..e89b75e --- /dev/null +++ b/sessionitemdelegate.h @@ -0,0 +1,20 @@ +#ifndef SESSIONITEMDELEGATE_H +#define SESSIONITEMDELEGATE_H + +#include + +class SessionItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit SessionItemDelegate(QObject *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +#endif // SESSIONITEMDELEGATE_H diff --git a/sessionslistmodel.cpp b/sessionslistmodel.cpp index 77239b8..cf13129 100644 --- a/sessionslistmodel.cpp +++ b/sessionslistmodel.cpp @@ -27,8 +27,19 @@ QVariant SessionsListModel::data(const QModelIndex &index, int role) const { const Session session = m_service->sessions().at(index.row()); switch (role) { case Qt::DisplayRole: + // Simple display for fallback - delegate will handle rich rendering return session.oneliner; + case Qt::UserRole: + // Store full session object for delegate access + return QVariant::fromValue(session); default: return QVariant(); } } + +Session SessionsListModel::getSessionByIndex(const QModelIndex &index) const { + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return Session(); // Return default-constructed session + } + return m_service->sessions().at(index.row()); +} diff --git a/sessionslistmodel.h b/sessionslistmodel.h index 1419bf4..fb56763 100644 --- a/sessionslistmodel.h +++ b/sessionslistmodel.h @@ -13,7 +13,9 @@ public: int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const; + QVariant data(const QModelIndex &index, int role) const override; + + Session getSessionByIndex(const QModelIndex &index) const; private: Service *m_service;