diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6113fee..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 9f9ca98..a8e562e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ build/ -build-universal/ +*~ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 9efc4b1..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(adhd VERSION 1.0.0 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set minimum macOS version -# Qt 6.8.6 requires macOS 11.0 (Big Sur) minimum -if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS version") -endif() - -find_package(Qt6 REQUIRED COMPONENTS - Core - Gui - Widgets -) - -qt_standard_project_setup() - -qt_add_executable(adhd - main.cpp - mainwindow.cpp - mainwindow.h - service.cpp - service.h - mainwindow.ui - 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. -# If you are developing for iOS or macOS you should consider setting an -# explicit, fixed bundle identifier manually though. -set_target_properties(adhd PROPERTIES - # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.adhd - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE TRUE - WIN32_EXECUTABLE $> -) - -target_link_libraries(adhd - PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -include(GNUInstallDirs) -install(TARGETS adhd - BUNDLE DESTINATION . - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - -qt_generate_deploy_app_script( - TARGET adhd - OUTPUT_SCRIPT deploy_script - NO_UNSUPPORTED_PLATFORM_ERROR -) -install(SCRIPT ${deploy_script}) diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user deleted file mode 100644 index 12de3df..0000000 --- a/CMakeLists.txt.user +++ /dev/null @@ -1,462 +0,0 @@ - - - - - - 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 - adhd - CMakeProjectManager.CMakeRunConfiguration. - adhd - false - true - true - true - /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Debug/adhd.app/Contents/MacOS - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - diff --git a/README.md b/README.md index 83df2ef..e4f74c8 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,29 @@ # Flowy.adhd -A macOS focus app that helps you stay on track by setting one intention per session. +We live in a noisy world. Unless you turn off your WiFi, millions of companies bid for your attention as you try to perform even the simplest tasks. + +This tool is a free, open-source application to help you stay on track. You set an intention before you work, and an overlay will remind you of your intention every x minutes. ![app](demo/app.png) ![desktop](demo/desktop.png) +## Install +At the moment, you must build and install the application yourself. There are minimum dependencies, so you essentially just need a C compiler. + ## How it works 1. Set your intention when you sit down at your computer -2. A persistent overlay keeps your focus visible while you work -3. End the session and mark it as a win or loss -4. Build a log of your sessions over time +2. A persistent overlay reminds you of your intention +3. Set whether today was a win (focused) or loss (distracted), and view your daily activity in a nice graph ## Philosophy -Stay aware of your intentions to avoid distractions. No todo manager needed—just focus on winning each session, one thing at a time. +In a world of fancy todo managers, for anyone with adhd, doing one thing at a time is the key. Win one intention at a time instead of planning, prioritizing, and juggling 100s of things. ## Features -- **No sign in required** - Start using immediately -- **Privacy first** - Sessions stored locally as markdown files +- **No sign in required** +- **Slick overlay to show current intention** +- **Lightweight** - Written in C with few dependencies, nothing bloated like electron +- **Privacy first** - No telemetry, works offline - **Session history** - Review your past wins and losses - -## Download - -Get the latest version from [releases](https://github.com/flowy-live/adhd/releases). - -## Roadmap - -- View past session notes -- Win/loss statistics visualizer -- Windows and Linux support diff --git a/TODO.md b/TODO.md index a442b66..4db2c59 100644 --- a/TODO.md +++ b/TODO.md @@ -1,45 +1,9 @@ -## 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 +- [ ] create a slick overlay mode +- [ ] create a simple window mode for adjusting the current intention +- [ ] remind me by flashing every x minutes (gradient orange) +- [ ] win / loss graph: set for each day with bit value in sqlite db +- [ ] adjust reminder interval +- [ ] install via homebrew +- [ ] create windows download +- [ ] launch on hackernews "for developers with adhd"...create an html page as well on flowylabs site diff --git a/build-universal.sh b/build-universal.sh deleted file mode 100755 index 9246a3f..0000000 --- a/build-universal.sh +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/bash - -# Build script for universal macOS bundle (arm64 + x86_64) - -set -e # Exit on error - -echo "=========================================" -echo "Building Universal macOS Bundle for adhd" -echo "=========================================" - -# Configuration -APP_NAME="adhd" -BUILD_DIR="build-universal" -BUILD_TYPE="Release" -ARCHITECTURES="arm64;x86_64" -QT_PATH="$HOME/Qt/6.8.6/macos" -APPLE_DEVELOPMENT_IDENTITY="Developer ID Application: Arjun Patel (RK9WWM4Q99)" - -# Clean previous build (optional - comment out to keep incremental builds) -# rm -rf "$BUILD_DIR" - -# Configure CMake -echo "" -echo "Configuring CMake for universal binary..." -echo "Minimum macOS version: 11.0 (Big Sur)" -cmake -B "$BUILD_DIR" \ - -DCMAKE_OSX_ARCHITECTURES="$ARCHITECTURES" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET="11.0" \ - -DCMAKE_PREFIX_PATH="$QT_PATH" \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - -# Build -echo "" -echo "Building..." -cmake --build "$BUILD_DIR" --config "$BUILD_TYPE" -j$(sysctl -n hw.ncpu) - -# Verify the binary is universal -echo "" -echo "Verifying binary architectures..." -if [ -f "$BUILD_DIR/$APP_NAME.app/Contents/MacOS/$APP_NAME" ]; then - lipo -info "$BUILD_DIR/$APP_NAME.app/Contents/MacOS/$APP_NAME" - echo "✓ Binary is universal" -else - echo "✗ Error: Binary not found at expected location" - exit 1 -fi - -# Deploy Qt dependencies -echo "" -echo "Deploying Qt dependencies..." -$QT_PATH/bin/macdeployqt "$BUILD_DIR/$APP_NAME.app" -always-overwrite -verbose=1 -echo "✓ Qt dependencies deployed" - -# Code signing -echo "" -echo "=========================================" -echo "Listing identities for code-signing" -echo "=========================================" -security find-identity -v -p codesigning - -echo "" -echo "=========================================" -echo "Code signing .app bundle" -echo "=========================================" -echo "Using: $APPLE_DEVELOPMENT_IDENTITY" - -cd "$BUILD_DIR" - -codesign --deep --force --timestamp --verify --verbose=1 \ - --sign "$APPLE_DEVELOPMENT_IDENTITY" \ - --options runtime "$APP_NAME.app" - -echo "" -echo "Verifying code signature..." -codesign --verify --deep --strict --verbose=1 "./$APP_NAME.app" -echo "✓ Code signing complete" - -# Create DMG -echo "" -echo "=========================================" -echo "Creating DMG" -echo "=========================================" -echo "NOTE: Install create-dmg if needed: https://github.com/sindresorhus/create-dmg" - -if ! command -v create-dmg &> /dev/null; then - echo "⚠ Warning: create-dmg not found. Install with: brew install create-dmg" - echo "Skipping DMG creation, notarization, and stapling." - cd .. - echo "" - echo "=========================================" - echo "Build completed (without DMG/notarization)" - echo "App bundle location: $BUILD_DIR/$APP_NAME.app" - echo "=========================================" - exit 0 -fi - -create-dmg "$APP_NAME.app" --identity="$APPLE_DEVELOPMENT_IDENTITY" --dmg-title="$APP_NAME" -# WORKAROUND: https://github.com/sindresorhus/create-dmg/issues/52 -mv *.dmg "$APP_NAME.dmg" 2>/dev/null || true - -echo "✓ DMG created: $APP_NAME.dmg" - -# Notarization -echo "" -echo "=========================================" -echo "Starting Notarization" -echo "=========================================" -echo "NOTE: Make sure you've run 'xcrun notarytool store-credentials' first" - -xcrun notarytool submit "$APP_NAME.dmg" \ - --keychain-profile "default" \ - --wait \ - --timeout "10m" - -echo "" -echo "=========================================" -echo "Stapling notarization ticket" -echo "=========================================" -xcrun stapler staple -v "$APP_NAME.dmg" -echo "✓ Notarization complete" - -# Optional: Create zip -echo "" -read -p "Create zip file? (y/N): " CREATE_ZIP -if [[ "$CREATE_ZIP" == "y" || "$CREATE_ZIP" == "Y" ]]; then - ZIP_NAME="$APP_NAME-universal.zip" - zip -9 "$ZIP_NAME" "$APP_NAME.dmg" - echo "✓ Created: $ZIP_NAME" -fi - -cd .. - -echo "" -echo "=========================================" -echo "Build completed successfully!" -echo "=========================================" -echo "App bundle: $BUILD_DIR/$APP_NAME.app" -echo "DMG: $BUILD_DIR/$APP_NAME.dmg" -echo "" -echo "To test: open $BUILD_DIR/$APP_NAME.dmg" -echo "=========================================" diff --git a/createsessiondialog.cpp b/createsessiondialog.cpp deleted file mode 100644 index db05854..0000000 --- a/createsessiondialog.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#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 deleted file mode 100644 index fe07bf8..0000000 --- a/createsessiondialog.h +++ /dev/null @@ -1,28 +0,0 @@ -#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 deleted file mode 100644 index dea1ee9..0000000 --- a/createsessiondialog.ui +++ /dev/null @@ -1,96 +0,0 @@ - - - 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 deleted file mode 100644 index 8efc133..0000000 --- a/endsessiondialog.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#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 deleted file mode 100644 index 0f55c5e..0000000 --- a/endsessiondialog.h +++ /dev/null @@ -1,32 +0,0 @@ -#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 deleted file mode 100644 index bd84603..0000000 --- a/endsessiondialog.ui +++ /dev/null @@ -1,221 +0,0 @@ - - - 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 deleted file mode 100644 index 4527f9e..0000000 --- a/focusoverlay.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "focusoverlay.h" -#include "ui_focusoverlay.h" -#include -#include -#include - -FocusOverlay::FocusOverlay(QWidget *parent) - : QWidget(parent), - ui(new Ui::FocusOverlay), - m_dragging(false) -{ - ui->setupUi(this); - - // Set window flags for always-on-top frameless window - // Use Qt::Window instead of Qt::Tool so it stays visible when app loses focus - setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); - setAttribute(Qt::WA_TranslucentBackground); - setAttribute(Qt::WA_ShowWithoutActivating); // Don't steal focus when showing - - // 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(); - - move(x, y); -} - -void FocusOverlay::mousePressEvent(QMouseEvent *event) -{ - if (event->button() == Qt::LeftButton) { - m_dragging = true; - m_dragStartPosition = event->globalPosition().toPoint() - frameGeometry().topLeft(); - event->accept(); - } -} - -void FocusOverlay::mouseMoveEvent(QMouseEvent *event) -{ - if (m_dragging && (event->buttons() & Qt::LeftButton)) { - move(event->globalPosition().toPoint() - m_dragStartPosition); - event->accept(); - } -} - -void FocusOverlay::mouseReleaseEvent(QMouseEvent *event) -{ - if (event->button() == Qt::LeftButton) { - m_dragging = false; - event->accept(); - } -} diff --git a/focusoverlay.h b/focusoverlay.h deleted file mode 100644 index ab02928..0000000 --- a/focusoverlay.h +++ /dev/null @@ -1,43 +0,0 @@ -#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(); - -protected: - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - -private: - Ui::FocusOverlay *ui; - void positionAtBottomCenter(); - - // For dragging - bool m_dragging; - QPoint m_dragStartPosition; -}; - -#endif // FOCUSOVERLAY_H diff --git a/focusoverlay.ui b/focusoverlay.ui deleted file mode 100644 index 7e3d7ce..0000000 --- a/focusoverlay.ui +++ /dev/null @@ -1,158 +0,0 @@ - - - FocusOverlay - - - - 0 - 0 - 350 - 102 - - - - - 0 - 0 - - - - - 350 - 80 - - - - - 350 - 102 - - - - 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: 4px 8px; - min-width: 50px; -} - -QPushButton:hover { - background-color: rgba(100, 100, 100, 255); -} - -QPushButton:pressed { - background-color: rgba(60, 60, 60, 255); -} - - - - 8 - - - 10 - - - 8 - - - 10 - - - 8 - - - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 12 - true - - - - Session Oneliner - - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter - - - - - - - - 10 - - - - Elapsed: 00:00:00 - - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter - - - - - - - - - - - - Reset - - - true - - - false - - - - - - - End Session - - - - - - - - - - diff --git a/main.cpp b/main.cpp deleted file mode 100644 index df55bee..0000000 --- a/main.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include -#include "mainwindow.h" -#include "service.h" - -int main(int argc, char *argv[]) { - std::cout << "Starting adhd program" << std::endl; - - QApplication a(argc, argv); - a.setStyle("fusion"); - - MainWindow w; - w.show(); - - return a.exec(); -} diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index 924343f..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#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 -#include "ui_mainwindow.h" - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), - m_service(new Service(this)), - m_focusOverlay(nullptr), - m_updatingActiveFields(false) -{ - ui = new Ui::MainWindow; - ui->setupUi(this); - setWindowTitle("flowy.adhd"); - - 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); - - // Load saved vault directory from settings - QSettings settings("flowy", "adhd"); - QString savedVaultPath = settings.value("vaultDirectory").toString(); - if (!savedVaultPath.isEmpty() && QDir(savedVaultPath).exists()) { - m_service->setVaultDirectory(savedVaultPath); - ui->vaultLabel->setText(savedVaultPath); - // Switch to Sessions tab if we have a valid vault - ui->tabWidget->setCurrentIndex(1); - } -} - -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(dir); - - // Save to settings for next launch - QSettings settings("flowy", "adhd"); - settings.setValue("vaultDirectory", 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 deleted file mode 100644 index 9030128..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include - -class Service; -class SessionsListModel; -class FocusOverlay; -class QTimer; - -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(); - void on_newSessionButton_clicked(); - void on_startSessionButton_clicked(); - void on_endSessionButton_clicked(); - - // 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 deleted file mode 100644 index e2378cb..0000000 --- a/mainwindow.ui +++ /dev/null @@ -1,289 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 400 - 600 - - - - MainWindow - - - - - - - 0 - - - - Settings - - - - - 80 - 210 - 221 - 16 - - - - Select a folder - - - Qt::AlignmentFlag::AlignCenter - - - - - - 130 - 240 - 121 - 32 - - - - Select vault - - - - - - true - - - - - - Sessions - - - - - - - - New Session - - - - - - - Start Selected - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - Active - - - - - - 0 - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - No session running. Start a session to begin. - - - Qt::AlignmentFlag::AlignCenter - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - - - - - true - - - - - 0 - 0 - 190 - 244 - - - - - - - - - Oneliner: - - - - - - - - - - Elapsed Time: - - - - - - - 00:00:00 - - - - - - - Created: - - - - - - - - - - - - - - - Modified: - - - - - - - - - - - - - - - Content: - - - - - - - Add notes, context, or anything related to this session... - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - End Session - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 400 - 30 - - - - - - - - diff --git a/namegenerator.cpp b/namegenerator.cpp deleted file mode 100644 index 93886d3..0000000 --- a/namegenerator.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "namegenerator.h" - -QString generate() { - return QString("generated-name-placeholder"); -} diff --git a/namegenerator.h b/namegenerator.h deleted file mode 100644 index 4aaa210..0000000 --- a/namegenerator.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef NAMEGENERATOR_H -#define NAMEGENERATOR_H - -#include - -QString generate(); - -#endif //NAMEGENERATOR_H diff --git a/run.sh b/run.sh deleted file mode 100755 index df21b0b..0000000 --- a/run.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -e - -BUILD_DIR=build - -if [ ! -d "$BUILD_DIR" ]; then - cmake -B$BUILD_DIR -S. -GNinja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_CXX_FLAGS="-Wall -Wextra" \ - -DCMAKE_PREFIX_PATH="$HOME/Qt/6.8.6/macos" \ - -DCMAKE_COLOR_DIAGNOSTICS=ON \ - -DCMAKE_CXX_FLAGS_INIT="-DQT_QML_DEBUG" -fi - -cmake --build $BUILD_DIR --target all - -$BUILD_DIR/adhd.app/Contents/MacOS/adhd diff --git a/service.cpp b/service.cpp deleted file mode 100644 index 3dc364e..0000000 --- a/service.cpp +++ /dev/null @@ -1,349 +0,0 @@ -#include "service.h" -#include -#include -#include -#include - -bool Session::isComplete() const { - return result != UNSPECIFIED; -} - -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() {} - -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::createUuid().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 = fileInfo.baseName(); // Get filename without extension - 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(); -} - -// 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 deleted file mode 100644 index 393eb55..0000000 --- a/service.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef SERVICE_H -#define SERVICE_H - -#include -#include -#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); - - // 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 deleted file mode 100644 index 183f543..0000000 --- a/sessionitemdelegate.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#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 deleted file mode 100644 index e89b75e..0000000 --- a/sessionitemdelegate.h +++ /dev/null @@ -1,20 +0,0 @@ -#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 deleted file mode 100644 index cf13129..0000000 --- a/sessionslistmodel.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#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: - // 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 deleted file mode 100644 index fb56763..0000000 --- a/sessionslistmodel.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 override; - - Session getSessionByIndex(const QModelIndex &index) const; - -private: - Service *m_service; -}; - -#endif // SESSIONSLISTMODEL_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..8e59dd1 --- /dev/null +++ b/src/main.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char *argv[]) { + printf("Welcome to Flowy.adhd\n"); + +}