From e2c9f8ce72b4f020467588f5680cca306e3d695f Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Mon, 9 Feb 2026 16:45:43 +0530 Subject: [PATCH 1/8] Implement Session History, Solidarity logging, and fix Windows runner errors --- lib/home.dart | 12 +--- lib/main.dart | 40 +++++------- lib/widgets/history.dart | 103 ++++++++++++++++++++++++++++++ lib/widgets/timer.dart | 35 ++++++++++ windows/runner/flutter_window.cpp | 15 ++--- 5 files changed, 161 insertions(+), 44 deletions(-) create mode 100644 lib/widgets/history.dart diff --git a/lib/home.dart b/lib/home.dart index 86e5d7a..3d4b68f 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -37,6 +37,7 @@ import 'package:innerpod/constants/colours.dart'; import 'package:innerpod/utils/word_wrap.dart'; import 'package:innerpod/widgets/instructions.dart'; import 'package:innerpod/widgets/timer.dart'; +import 'package:innerpod/widgets/history.dart'; /// A widget for the actuall app's main home page. @@ -108,14 +109,7 @@ class HomeState extends State with SingleTickerProviderStateMixin { final List _pages = [ const Timer(), const Instructions(), - const Column( - children: [ - Gap(100), - Text('Coming Soon'), - Gap(100), - Icon(Icons.chat, size: 150), - ], - ), + const History(), ]; @override @@ -229,7 +223,7 @@ class HomeState extends State with SingleTickerProviderStateMixin { label: 'Text', ), BottomNavigationBarItem( - icon: Icon(Icons.settings), + icon: Icon(Icons.history), label: 'History', ), ], diff --git a/lib/main.dart b/lib/main.dart index 4936861..55494d6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,6 +26,7 @@ library; import 'package:flutter/material.dart'; +import 'package:solidpod/solidpod.dart'; import 'package:innerpod/home.dart'; @@ -60,29 +61,20 @@ class InnerPod extends StatelessWidget { /// at that time. The login token and the security key are (optionally) /// cached so that the login information is not required every time. - // TODO 20240708 gjw COMMENTED OUT FOR NOW BUT INTEND TO USE. - - // return const SolidLogin( - // title: 'MANAGE YOUR INNER POD', - // required: false, - // image: AssetImage('assets/images/inner_image.jpg'), - // logo: AssetImage('assets/images/inner_icon.png'), - // continueButtonStyle: ContinueButtonStyle( - // text: 'Session', - // background: Colors.lightGreenAccent, - // ), - // infoButtonStyle: InfoButtonStyle( - // tooltip: 'Browse to the InnerPod home page.', - // ), - // // registerButtonStyle: registerButtonStyle( - // // text: 'REG', - // // ), - // link: 'https://github.com/gjwgit/innerpod/blob/dev/README.md', - // child: Home(), - // ); - - // OR - - return const Home(); + return const SolidLogin( + title: 'MANAGE YOUR INNER POD', + required: false, + image: AssetImage('assets/images/inner_image.jpg'), + logo: AssetImage('assets/images/inner_icon.png'), + continueButtonStyle: ContinueButtonStyle( + text: 'Session', + background: Colors.lightGreenAccent, + ), + infoButtonStyle: InfoButtonStyle( + tooltip: 'Browse to the InnerPod home page.', + ), + link: 'https://github.com/gjwgit/innerpod/blob/dev/README.md', + child: Home(), + ); } } diff --git a/lib/widgets/history.dart b/lib/widgets/history.dart new file mode 100644 index 0000000..d549880 --- /dev/null +++ b/lib/widgets/history.dart @@ -0,0 +1,103 @@ +/// A table of past sessions logged to the user's Solid Pod. +// +// Time-stamp: <2026-02-09 16:45:00 Amogh Hosamane> +// +/// Copyright (C) 2024-2026, Togaware Pty Ltd +/// +/// Licensed under the GNU General Public License, Version 3 (the "License"); + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:solidpod/solidpod.dart'; +import 'dart:convert'; + +class History extends StatefulWidget { + const History({super.key}); + + @override + State createState() => _HistoryState(); +} + +class _HistoryState extends State { + List> _sessions = []; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _loadSessions(); + } + + Future _loadSessions() async { + setState(() { + _isLoading = true; + }); + + try { + // Assuming sessions are stored in 'sessions.json' in the Pod + String? content = await readPod('sessions.json'); + if (content != null && content.isNotEmpty) { + List jsonList = jsonDecode(content); + setState(() { + _sessions = jsonList.map((item) { + final start = DateTime.parse(item['start']); + final end = DateTime.parse(item['end']); + return { + 'date': DateFormat('yyyy-MM-dd').format(start), + 'start': DateFormat('HH:mm:ss').format(start), + 'end': DateFormat('HH:mm:ss').format(end), + }; + }).toList(); + }); + } + } catch (e) { + debugPrint('Error loading sessions: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Session History'), + automaticallyImplyLeading: false, // Don't show back button + actions: [ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _loadSessions, + ), + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _sessions.isEmpty + ? const Center(child: Text('No sessions recorded yet.')) + : Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: DataTable( + columns: const [ + DataColumn(label: Text('Date')), + DataColumn(label: Text('Start')), + DataColumn(label: Text('End')), + ], + rows: _sessions.map((session) { + return DataRow(cells: [ + DataCell(Text(session['date']!)), + DataCell(Text(session['start']!)), + DataCell(Text(session['end']!)), + ]); + }).toList(), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/timer.dart b/lib/widgets/timer.dart index 57688f5..dd7f363 100644 --- a/lib/widgets/timer.dart +++ b/lib/widgets/timer.dart @@ -30,6 +30,8 @@ import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:circular_countdown_timer/circular_countdown_timer.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:solidpod/solidpod.dart'; +import 'dart:convert'; import 'package:innerpod/constants/audio.dart'; import 'package:innerpod/constants/spacing.dart'; @@ -76,6 +78,10 @@ class TimerState extends State { var _audioDuration = Duration.zero; + // Track the start time of a session. + + DateTime? _startTime; + //////////////////////////////////////////////////////////////////////// // CONSTANTS //////////////////////////////////////////////////////////////////////// @@ -125,6 +131,7 @@ class TimerState extends State { _reset(); _stopSleep(); _isGuided = false; + _startTime = DateTime.now(); // Good to wait a second before starting the audio after tapping the button, // otherwise it feels rushed. @@ -167,6 +174,7 @@ class TimerState extends State { _reset(); _stopSleep(); _isGuided = true; + _startTime = DateTime.now(); // Good to wait a second before starting the audio after tapping the button, // otherwise it feels rushed. @@ -215,6 +223,32 @@ class TimerState extends State { _reset(); _allowSleep(); + await _saveSession(); + } + + Future _saveSession() async { + if (_startTime == null) return; + + final endTime = DateTime.now(); + final session = { + 'start': _startTime!.toIso8601String(), + 'end': endTime.toIso8601String(), + }; + + try { + String? content = await readPod('sessions.json'); + List sessions = []; + if (content != null && content.isNotEmpty) { + sessions = jsonDecode(content); + } + sessions.add(session); + await writePod('sessions.json', jsonEncode(sessions)); + logMessage('Session saved to Pod'); + } catch (e) { + logMessage('Error saving session to Pod: $e'); + } + + _startTime = null; } //////////////////////////////////////////////////////////////////////// @@ -257,6 +291,7 @@ minutes, beginning and ending with three chimes. dingDong(_player); _controller.restart(); _stopSleep(); + _startTime = DateTime.now(); }, fontWeight: FontWeight.bold, backgroundColor: Colors.lightGreenAccent.shade100, diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 955ee30..0270d2e 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -4,7 +4,7 @@ #include "flutter/generated_plugin_registrant.h" -FlutterWindow::FlutterWindow(const flutter::DartProject& project) +FlutterWindow::FlutterWindow(const flutter::DartProject &project) : project_(project) {} FlutterWindow::~FlutterWindow() {} @@ -27,14 +27,7 @@ bool FlutterWindow::OnCreate() { RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); + flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); return true; } @@ -59,12 +52,12 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, if (result) { return *result; } - } - switch (message) { + switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; + } } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); From ac37d3e30f898f54ec653ab73fbbbd19a153e3ff Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 16:21:51 +0530 Subject: [PATCH 2/8] feat: use RDF/TTL for session storage, fix imports, fix links --- .agent/workflows/setup-flutter.md | 181 +++++++++ DEVELOPMENT_GUIDE.md | 437 ++++++++++++++++++++++ GETTING_STARTED.md | 331 +++++++++++++++++ README.md | 4 +- SESSION_ENHANCEMENTS.md | 593 ++++++++++++++++++++++++++++++ lib/home.dart | 2 +- lib/main.dart | 2 +- lib/utils/session_logic.dart | 77 ++++ lib/widgets/history.dart | 26 +- lib/widgets/timer.dart | 13 +- pubspec.yaml | 7 + test/session_logic_test.dart | 74 ++++ 12 files changed, 1725 insertions(+), 22 deletions(-) create mode 100644 .agent/workflows/setup-flutter.md create mode 100644 DEVELOPMENT_GUIDE.md create mode 100644 GETTING_STARTED.md create mode 100644 SESSION_ENHANCEMENTS.md create mode 100644 lib/utils/session_logic.dart create mode 100644 test/session_logic_test.dart diff --git a/.agent/workflows/setup-flutter.md b/.agent/workflows/setup-flutter.md new file mode 100644 index 0000000..292c23d --- /dev/null +++ b/.agent/workflows/setup-flutter.md @@ -0,0 +1,181 @@ +--- +description: How to set up Flutter development environment on Windows +--- + +# Flutter Setup Workflow for Windows + +## 1. Install Flutter SDK + +### Option A: Using Git (Recommended) +```powershell +# Navigate to a directory where you want to install Flutter (e.g., C:\src) +cd C:\ +mkdir src +cd src + +# Clone the Flutter repository +git clone https://github.com/flutter/flutter.git -b stable + +# Add Flutter to your PATH +# Add C:\src\flutter\bin to your system PATH environment variable +``` + +### Option B: Download ZIP +1. Download Flutter SDK from: https://docs.flutter.dev/get-started/install/windows +2. Extract to `C:\src\flutter` +3. Add `C:\src\flutter\bin` to your system PATH + +### Add to PATH (PowerShell as Administrator) +```powershell +# Get current PATH +$currentPath = [Environment]::GetEnvironmentVariable("Path", "User") + +# Add Flutter to PATH (adjust path if you installed elsewhere) +[Environment]::SetEnvironmentVariable( + "Path", + "$currentPath;C:\src\flutter\bin", + "User" +) +``` + +## 2. Verify Flutter Installation + +// turbo +```powershell +# Restart your terminal, then run: +flutter --version +``` + +// turbo +```powershell +# Check for any missing dependencies +flutter doctor +``` + +## 3. Install Required Dependencies + +Based on `flutter doctor` output, you may need to install: + +### Visual Studio (for Windows desktop development) +- Download Visual Studio 2022 Community: https://visualstudio.microsoft.com/downloads/ +- During installation, select "Desktop development with C++" + +### Android Studio (for Android development) +- Download from: https://developer.android.com/studio +- Install Android SDK and Android SDK Command-line Tools + +### Chrome (for web development) +- Already installed on most systems + +## 4. Accept Android Licenses (if developing for Android) + +```powershell +flutter doctor --android-licenses +``` + +## 5. Set Up InnerPod Project + +// turbo +```powershell +# Navigate to the innerpod directory +cd c:\Desktop\innerpod + +# Get all Flutter dependencies +flutter pub get +``` + +## 6. Verify Project Setup + +// turbo +```powershell +# Check for any issues +flutter doctor -v + +# List available devices +flutter devices +``` + +## 7. Build and Run the App + +### For Windows Desktop: +// turbo +```powershell +flutter run -d windows +``` + +### For Web: +```powershell +flutter run -d chrome +``` + +### For Android (with device connected or emulator running): +```powershell +flutter run -d android +``` + +## 8. Build Release Version + +### Windows: +```powershell +flutter build windows +``` + +### Web: +```powershell +flutter build web +``` + +### Android: +```powershell +flutter build apk +``` + +## Troubleshooting + +### Issue: Flutter command not found +- Solution: Restart your terminal after adding Flutter to PATH +- Or manually add to current session: `$env:Path += ";C:\src\flutter\bin"` + +### Issue: Missing Visual Studio +- Solution: Install Visual Studio 2022 with C++ desktop development workload + +### Issue: Android licenses not accepted +- Solution: Run `flutter doctor --android-licenses` and accept all + +### Issue: Pub get fails +- Solution: Check internet connection, try `flutter pub cache repair` + +## Next Steps + +Once Flutter is set up: +1. Fork the innerpod repository on GitHub +2. Clone your fork locally +3. Create a new branch for your changes +4. Make your modifications +5. Test thoroughly +6. Submit a pull request + +## Useful Commands + +```powershell +# Update Flutter SDK +flutter upgrade + +# Clean build artifacts +flutter clean + +# Analyze code for issues +flutter analyze + +# Run tests +flutter test + +# Format code +dart format . + +# Check for outdated packages +flutter pub outdated + +# Upgrade packages +flutter pub upgrade +``` diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..6ad065f --- /dev/null +++ b/DEVELOPMENT_GUIDE.md @@ -0,0 +1,437 @@ +# InnerPod Development Guide + +## 📚 Table of Contents +1. [Project Overview](#project-overview) +2. [Session Recording Architecture](#session-recording-architecture) +3. [Solid Pod Integration](#solid-pod-integration) +4. [Development Setup](#development-setup) +5. [Key Features](#key-features) +6. [Code Structure](#code-structure) +7. [Testing](#testing) +8. [Contributing](#contributing) + +## Project Overview + +InnerPod is a meditation timer app built with Flutter that: +- Provides guided and unguided meditation sessions +- Records session data to encrypted Solid Pods +- Displays session history +- Works offline (Pod connection is optional) + +**Tech Stack:** +- **Framework:** Flutter 3.2.5+ +- **Language:** Dart +- **Storage:** Solid Pod (encrypted, decentralized) +- **Key Packages:** + - `solidpod: ^0.7.4` - Solid Pod integration + - `circular_countdown_timer: ^0.2.3` - Timer UI + - `audioplayers: ^6.1.0` - Audio playback + - `intl: ^0.20.2` - Date/time formatting + +## Session Recording Architecture + +### How It Works + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Starts Session │ +│ (Start/Intro/Guided button) │ +└────────────────────────┬────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Timer Widget (_startTime recorded) │ +│ Session in Progress │ +└────────────────────────┬────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Session Completes │ +│ _complete() method is called │ +└────────────────────────┬────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ _saveSession() Method │ +│ 1. Check if user is logged into Pod │ +│ 2. Read existing sessions.ttl from Pod │ +│ 3. Parse TTL data of sessions │ +│ 4. Append new session {start, end} as RDF │ +│ 5. Write back to Pod (encrypted) │ +└────────────────────────┬────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ History Widget │ +│ - Reads sessions.ttl from Pod │ +│ - Displays in DataTable format │ +│ - Shows: Date, Start Time, End Time │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Data Format + +Sessions are stored in `sessions.ttl` on the user's Solid Pod: + +```ttl +@prefix : <#>. +@prefix xsd: . + +:session_1739097000000 a :Session; + :start "2026-02-09T10:30:00.000Z"^^xsd:dateTime; + :end "2026-02-09T10:50:00.000Z"^^xsd:dateTime. + +:session_1739175300000 a :Session; + :start "2026-02-10T08:15:00.000Z"^^xsd:dateTime; + :end "2026-02-10T08:35:00.000Z"^^xsd:dateTime. +``` + +**Format Details:** +- ISO 8601 timestamp format +- UTC timezone +- Millisecond precision + +### Key Code Locations + +#### 1. Session Recording (`lib/widgets/timer.dart`) + +```dart +// Lines 229-252: Session saving logic +Future _saveSession() async { + if (_startTime == null) return; + + final endTime = DateTime.now(); + final session = { + 'start': _startTime!.toIso8601String(), + 'end': endTime.toIso8601String(), + }; + + try { + // Read existing sessions from Pod + String? content = await readPod('sessions.ttl'); + + // Append new session using helper + String newContent = addSession(content, session); + + // Write back to Pod + await writePod('sessions.ttl', newContent); + logMessage('Session saved to Pod'); + } catch (e) { + logMessage('Error saving session to Pod: $e'); + } + + _startTime = null; +} +``` + +#### 2. History Display (`lib/widgets/history.dart`) + +```dart +// Lines 31-60: Loading sessions from Pod +Future _loadSessions() async { + setState(() { + _isLoading = true; + }); + + try { + String? content = await readPod('sessions.ttl'); + List jsonList = parseSessions(content); + if (jsonList.isNotEmpty) { + setState(() { + _sessions = jsonList.map((item) { + final start = DateTime.parse(item['start']); + final end = DateTime.parse(item['end']); + return { + 'date': DateFormat('yyyy-MM-dd').format(start), + 'start': DateFormat('HH:mm:ss').format(start), + 'end': DateFormat('HH:mm:ss').format(end), + }; + }).toList(); + }); + } + } catch (e) { + debugPrint('Error loading sessions: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } +} +``` + +## Solid Pod Integration + +### What is a Solid Pod? + +Solid (Social Linked Data) is a decentralized web platform where: +- Users own their data +- Data is stored in personal "Pods" (Personal Online Data stores) +- Apps request permission to access data +- Data is encrypted and private by default + +### How InnerPod Uses Solid Pods + +1. **Optional Login** (`lib/main.dart`): + ```dart + SolidLogin( + title: 'MANAGE YOUR INNER POD', + required: false, // App works without login + child: Home(), + ) + ``` + +2. **Reading Data**: + ```dart + String? content = await readPod('sessions.ttl'); + ``` + +3. **Writing Data**: + ```dart + await writePod('sessions.ttl', newContent); + ``` + +### Benefits of Solid Pod Storage + +- ✅ **Privacy:** Data is encrypted and only accessible to the user +- ✅ **Ownership:** Users control their data, not the app developer +- ✅ **Portability:** Data can be accessed by other Solid-compatible apps +- ✅ **Decentralized:** No central server storing user data +- ✅ **Secure:** Uses WebID authentication + +## Development Setup + +### Prerequisites + +1. **Flutter SDK** (3.2.5 or higher) +2. **Dart SDK** (included with Flutter) +3. **Git** +4. **IDE:** VS Code or Android Studio +5. **Platform-specific tools:** + - Windows: Visual Studio 2022 with C++ desktop development + - Android: Android Studio + Android SDK + - Web: Chrome browser + +### Getting Started + +```bash +# 1. Fork the repository on GitHub +# Visit: https://github.com/gjwgit/innerpod + +# 2. Clone your fork +git clone https://github.com/YOUR_USERNAME/innerpod.git +cd innerpod + +# 3. Install dependencies +flutter pub get + +# 4. Check setup +flutter doctor + +# 5. Run the app +flutter run -d windows # or chrome, android, etc. +``` + +## Key Features + +### 1. Timer Widget (`lib/widgets/timer.dart`) + +**Responsibilities:** +- Countdown timer display +- Session management (start, pause, resume, reset) +- Audio playback (bells, guided meditation) +- Session recording + +**Key Methods:** +- `_intro()` - Plays intro audio then starts session +- `_guided()` - Plays full guided meditation +- `_complete()` - Called when session ends +- `_saveSession()` - Saves session data to Pod + +### 2. History Widget (`lib/widgets/history.dart`) + +**Responsibilities:** +- Display past sessions in a table +- Load sessions from Solid Pod +- Refresh functionality + +**Features:** +- Date formatting (yyyy-MM-dd) +- Time formatting (HH:mm:ss) +- Loading state indicator +- Empty state message +- Pull-to-refresh + +### 3. Home Widget (`lib/home.dart`) + +**Responsibilities:** +- Navigation between tabs (Home, Instructions, History) +- App bar with version info +- About dialog + +## Code Structure + +``` +innerpod/ +├── lib/ +│ ├── main.dart # App entry point, Solid login +│ ├── home.dart # Main navigation & app bar +│ ├── widgets/ +│ │ ├── timer.dart # Timer & session recording +│ │ ├── history.dart # Session history display +│ │ ├── instructions.dart # Help/instructions +│ │ ├── app_button.dart # Custom button widget +│ │ └── app_circular_countdown_timer.dart +│ ├── constants/ +│ │ ├── colours.dart # Color definitions +│ │ ├── audio.dart # Audio file paths +│ │ └── spacing.dart # Layout constants +│ └── utils/ +│ ├── ding_dong.dart # Bell sound helper +│ ├── log_message.dart # Logging utility +│ └── word_wrap.dart # Text formatting +├── assets/ +│ ├── images/ # App icons & images +│ └── sounds/ # Audio files +├── pubspec.yaml # Dependencies +└── README.md # Documentation +``` + +## Testing + +### Manual Testing Checklist + +#### Session Recording: +- [ ] Start a session without Pod login (should not crash) +- [ ] Start a session with Pod login (should save) +- [ ] Complete multiple sessions (should append to list) +- [ ] Check sessions.json in Pod (should be valid JSON) + +#### History Display: +- [ ] View history without Pod login (should show empty state) +- [ ] View history with sessions (should display table) +- [ ] Refresh history (should reload data) +- [ ] Check date/time formatting (should be readable) + +#### Edge Cases: +- [ ] Network interruption during save +- [ ] Corrupted sessions.json file +- [ ] Very long session (hours) +- [ ] Session started but app closed before completion + +### Running Tests + +```bash +# Run all tests +flutter test + +# Run with coverage +flutter test --coverage + +# Analyze code +flutter analyze +``` + +## Contributing + +### Workflow + +1. **Fork & Clone** + ```bash + git clone https://github.com/YOUR_USERNAME/innerpod.git + ``` + +2. **Create Branch** + ```bash + git checkout -b feature/your-feature-name + ``` + +3. **Make Changes** + - Follow existing code style + - Add comments for complex logic + - Update documentation + +4. **Test** + ```bash + flutter test + flutter analyze + dart format . + ``` + +5. **Commit** + ```bash + git add . + git commit -m "feat: add session duration calculation" + ``` + +6. **Push & PR** + ```bash + git push origin feature/your-feature-name + # Create Pull Request on GitHub + ``` + +### Code Style + +- Follow [Dart style guide](https://dart.dev/guides/language/effective-dart/style) +- Use `dart format .` before committing +- Add doc comments for public APIs +- Keep functions focused and small + +### Potential Enhancements + +Here are some ideas for improving the session recording feature: + +1. **Session Duration Display** + - Calculate and display session duration in history + - Show statistics (total time, average session length) + +2. **Session Types** + - Track session type (Start, Intro, Guided) + - Filter history by session type + +3. **Session Notes** + - Allow users to add notes after a session + - Display notes in history + +4. **Data Visualization** + - Charts showing meditation frequency + - Calendar view of sessions + - Streak tracking + +5. **Export Functionality** + - Export sessions to CSV + - Share session data + +6. **Offline Support** + - Queue sessions when offline + - Sync to Pod when connection restored + +7. **Session Reminders** + - Daily meditation reminders + - Customizable notification times + +8. **Enhanced Encryption** + - Additional encryption layer for sensitive notes + - Backup/restore functionality + +## Resources + +### Documentation +- [Flutter Docs](https://docs.flutter.dev/) +- [Dart Language Tour](https://dart.dev/guides/language/language-tour) +- [Solid Project](https://solidproject.org/) +- [solidpod Package](https://pub.dev/packages/solidpod) + +### InnerPod Specific +- [GitHub Repository](https://github.com/gjwgit/innerpod) +- [Online Demo](https://innerpod.solidcommunity.au) +- [Changelog](https://github.com/gjwgit/innerpod/blob/dev/CHANGELOG.md) + +### Community +- [Flutter Community](https://flutter.dev/community) +- [Solid Community](https://forum.solidproject.org/) + +--- + +**Happy Coding! 🧘‍♂️** + +For questions or issues, please open an issue on GitHub or contact the maintainers. diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000..3cd1d49 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,331 @@ +# InnerPod: Getting Started Guide + +## 🎉 Welcome! + +This guide will help you get started with contributing to InnerPod, specifically focusing on the session recording and history features. + +## 📋 What You Need to Know + +### ✅ Good News: Features Already Implemented! + +The session recording and history features you mentioned are **already implemented** in InnerPod! Here's what's working: + +1. **Automatic Session Recording** + - Sessions are automatically saved when completed + - Data includes start time and end time + - Stored encrypted in user's Solid Pod + +2. **History Display** + - View all past sessions in a table + - Shows date, start time, and end time + - Accessible via the History tab in the bottom navigation + +3. **Solid Pod Integration** + - Uses `solidpod` package from pub.dev + - Data is encrypted and private + - Works offline (Pod connection is optional) + +### 📁 Key Files to Understand + +| File | Purpose | Lines of Interest | +|------|---------|-------------------| +| `lib/widgets/timer.dart` | Session timer & recording | 229-252 (_saveSession) | +| `lib/widgets/history.dart` | Display session history | 31-60 (_loadSessions) | +| `lib/main.dart` | App entry & Solid login | 64-78 (SolidLogin) | +| `lib/home.dart` | Navigation & app structure | 109-113 (pages list) | + +## 🚀 Quick Start + +### Step 1: Install Flutter + +**Windows Setup:** +```powershell +# 1. Download Flutter SDK +# Visit: https://docs.flutter.dev/get-started/install/windows + +# 2. Extract to C:\src\flutter + +# 3. Add to PATH (PowerShell as Administrator) +$currentPath = [Environment]::GetEnvironmentVariable("Path", "User") +[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\src\flutter\bin", "User") + +# 4. Restart terminal and verify +flutter --version +flutter doctor +``` + +**Detailed instructions:** See `.agent/workflows/setup-flutter.md` + +### Step 2: Set Up the Project + +```powershell +# Navigate to innerpod directory +cd c:\Desktop\innerpod + +# Install dependencies +flutter pub get + +# Check for issues +flutter doctor + +# List available devices +flutter devices +``` + +### Step 3: Run the App + +```powershell +# For Windows desktop +flutter run -d windows + +# For web browser +flutter run -d chrome + +# For Android (with device/emulator) +flutter run -d android +``` + +## 📚 Documentation Created for You + +I've created three comprehensive guides to help you: + +### 1. **DEVELOPMENT_GUIDE.md** +Complete development documentation covering: +- Project architecture overview +- Session recording implementation details +- Solid Pod integration explanation +- Code structure and organization +- Testing strategies +- Contribution guidelines + +**When to use:** Understanding how the app works, architecture decisions, and best practices + +### 2. **SESSION_ENHANCEMENTS.md** +Enhancement ideas with implementation details: +- 10 potential improvements (from easy to advanced) +- Code examples for each enhancement +- Effort estimates and priority matrix +- Recommended implementation order + +**When to use:** Planning new features, looking for contribution ideas + +### 3. **.agent/workflows/setup-flutter.md** +Step-by-step Flutter setup workflow: +- Installing Flutter SDK on Windows +- Setting up development environment +- Configuring PATH variables +- Verifying installation +- Running the app + +**When to use:** Setting up your development environment + +## 🎯 Suggested Next Steps + +### Option A: Get Familiar with the Codebase + +1. **Set up Flutter** (use `/setup-flutter` workflow) +2. **Run the app** locally +3. **Explore the code:** + - Read `lib/main.dart` to understand app structure + - Study `lib/widgets/timer.dart` to see session recording + - Review `lib/widgets/history.dart` to see data display +4. **Test the features:** + - Create a Solid Pod account (optional) + - Run a meditation session + - View the history tab + - Check the saved data + +### Option B: Start Contributing + +1. **Fork the repository** on GitHub + - Visit: https://github.com/gjwgit/innerpod + - Click "Fork" button + +2. **Clone your fork** + ```bash + git clone https://github.com/YOUR_USERNAME/innerpod.git + cd innerpod + ``` + +3. **Pick a quick win** from SESSION_ENHANCEMENTS.md + - Recommended first task: **Session Duration Display** + - Estimated time: 30 minutes + - High impact, low effort + +4. **Create a branch** + ```bash + git checkout -b feature/session-duration + ``` + +5. **Implement the feature** + - Follow the code examples in SESSION_ENHANCEMENTS.md + - Test thoroughly + - Commit your changes + +6. **Submit a Pull Request** + - Push to your fork + - Create PR on original repository + - Describe your changes + +## 💡 Quick Reference + +### Running Commands + +```powershell +# Install dependencies +flutter pub get + +# Run app (Windows) +flutter run -d windows + +# Build release +flutter build windows + +# Run tests +flutter test + +# Analyze code +flutter analyze + +# Format code +dart format . +``` + +### Project Structure + +``` +innerpod/ +├── lib/ +│ ├── main.dart # App entry point +│ ├── home.dart # Main navigation +│ ├── widgets/ +│ │ ├── timer.dart # ⭐ Session recording +│ │ └── history.dart # ⭐ Session history +│ ├── constants/ +│ └── utils/ +├── assets/ # Images & sounds +├── pubspec.yaml # Dependencies +└── README.md # Project info +``` + +### Key Packages Used + +```yaml +solidpod: ^0.7.4 # Solid Pod integration +circular_countdown_timer: ^0.2.3 # Timer UI +audioplayers: ^6.1.0 # Audio playback +intl: ^0.20.2 # Date/time formatting +``` + +## 🔍 Understanding Session Recording + +### How It Works (Simplified) + +``` +1. User starts session + ↓ +2. App records start time + ↓ +3. Timer counts down + ↓ +4. Session completes + ↓ +5. App records end time + ↓ +6. Data saved to Pod (if logged in) + { + "start": "2026-02-09T10:30:00.000Z", + "end": "2026-02-09T10:50:00.000Z" + } + ↓ +7. History tab displays all sessions +``` + +### Data Storage + +**Location:** User's Solid Pod +**File:** `sessions.json` +**Format:** JSON array of session objects +**Encryption:** Handled by Solid Pod + +**Example:** +```json +[ + { + "start": "2026-02-09T10:30:00.000Z", + "end": "2026-02-09T10:50:00.000Z" + }, + { + "start": "2026-02-10T08:15:00.000Z", + "end": "2026-02-10T08:35:00.000Z" + } +] +``` + +## 🎨 Easy First Contributions + +### 1. Add Session Duration (30 min) + +**File:** `lib/widgets/history.dart` +**Change:** Calculate and display session duration +**Difficulty:** 🟢 Beginner + +### 2. Track Session Type (1 hour) + +**File:** `lib/widgets/timer.dart` +**Change:** Add 'type' field (start/intro/guided) +**Difficulty:** 🟢 Beginner + +### 3. Show Statistics (2 hours) + +**File:** `lib/widgets/history.dart` +**Change:** Display total sessions, total time, average +**Difficulty:** 🟡 Intermediate + +**See SESSION_ENHANCEMENTS.md for detailed implementation guides!** + +## 🤝 Getting Help + +### Resources + +- **Flutter Docs:** https://docs.flutter.dev/ +- **Dart Language:** https://dart.dev/guides +- **Solid Project:** https://solidproject.org/ +- **InnerPod GitHub:** https://github.com/gjwgit/innerpod + +### Common Issues + +**Q: Flutter command not found** +A: Restart terminal after adding to PATH, or run: +```powershell +$env:Path += ";C:\src\flutter\bin" +``` + +**Q: Visual Studio required error** +A: Install Visual Studio 2022 with C++ desktop development workload + +**Q: How do I test without a Solid Pod?** +A: App works offline! Just click "SESSION" on login screen + +**Q: Where is session data stored locally?** +A: Only in Solid Pod. Without login, sessions aren't saved (by design) + +## 🎯 Your Mission (If You Choose to Accept) + +1. ✅ Set up Flutter development environment +2. ✅ Run InnerPod locally +3. ✅ Understand session recording architecture +4. ✅ Pick an enhancement from SESSION_ENHANCEMENTS.md +5. ✅ Implement and test +6. ✅ Submit a Pull Request +7. ✅ Celebrate! 🎉 + +## 📞 Contact + +- **GitHub Issues:** https://github.com/gjwgit/innerpod/issues +- **Original Author:** Graham Williams +- **Project:** Togaware + +--- + +**Happy coding! May your meditation sessions be peaceful and your code bug-free! 🧘‍♂️✨** diff --git a/README.md b/README.md index e083240..02ba816 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ the [Android app](https://play.google.com/store/apps/details?id=com.togaware.innerpod). For more information on the Solid project visit the [Solid Project -AU](https://solidporject.au) site. +AU](https://solidproject.org) site. ## Acknowledgements @@ -134,7 +134,7 @@ The instructions for meditating by John Main are from [WCCM](https://wccm.org). The bell is Tibetan bowl_left hit.wav by -[dersinnsspace](https://freesound.org/s/417117/). License: Creative +[dersinnsspace](https://freesound.org/people/dersinnsspace/sounds/417117/). License: Creative Commons 0 ## Contributing diff --git a/SESSION_ENHANCEMENTS.md b/SESSION_ENHANCEMENTS.md new file mode 100644 index 0000000..7ed6cb8 --- /dev/null +++ b/SESSION_ENHANCEMENTS.md @@ -0,0 +1,593 @@ +# Session Recording Enhancement Ideas + +This document outlines potential enhancements to the InnerPod session recording and history features. + +## 🎯 Quick Wins (Easy to Implement) + +### 1. Session Duration Display + +**Current State:** Only start and end times are shown +**Enhancement:** Calculate and display session duration + +**Implementation:** +```dart +// In lib/widgets/history.dart +_sessions = jsonList.map((item) { + final start = DateTime.parse(item['start']); + final end = DateTime.parse(item['end']); + final duration = end.difference(start); + + return { + 'date': DateFormat('yyyy-MM-dd').format(start), + 'start': DateFormat('HH:mm:ss').format(start), + 'end': DateFormat('HH:mm:ss').format(end), + 'duration': '${duration.inMinutes} min ${duration.inSeconds % 60} sec', + }; +}).toList(); + +// Add duration column to DataTable +DataColumn(label: Text('Duration')), +// ... +DataCell(Text(session['duration']!)), +``` + +**Effort:** 🟢 Low (30 minutes) + +--- + +### 2. Session Type Tracking + +**Current State:** No distinction between Start/Intro/Guided sessions +**Enhancement:** Track and display session type + +**Implementation:** +```dart +// In lib/widgets/timer.dart, modify _saveSession() +final session = { + 'start': _startTime!.toIso8601String(), + 'end': endTime.toIso8601String(), + 'type': _sessionType, // Add this field +}; + +// Add state variable +String _sessionType = 'start'; // or 'intro', 'guided' + +// Update in each button handler +onPressed: () { + _sessionType = 'intro'; + _intro(); +} +``` + +**Effort:** 🟢 Low (1 hour) + +--- + +### 3. Total Statistics Display + +**Current State:** No summary statistics +**Enhancement:** Show total meditation time, session count, average duration + +**Implementation:** +```dart +// In lib/widgets/history.dart +Widget _buildStatistics() { + if (_sessions.isEmpty) return SizedBox.shrink(); + + int totalMinutes = 0; + for (var session in _sessions) { + final start = DateTime.parse(session['start']); + final end = DateTime.parse(session['end']); + totalMinutes += end.difference(start).inMinutes; + } + + final avgMinutes = totalMinutes ~/ _sessions.length; + + return Card( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + Text('Total Sessions: ${_sessions.length}'), + Text('Total Time: ${totalMinutes ~/ 60}h ${totalMinutes % 60}m'), + Text('Average Duration: $avgMinutes minutes'), + ], + ), + ), + ); +} +``` + +**Effort:** 🟡 Medium (2 hours) + +--- + +## 🚀 Medium Enhancements + +### 4. Session Notes + +**Enhancement:** Allow users to add notes after completing a session + +**Data Format:** +```json +{ + "start": "2026-02-09T10:30:00.000Z", + "end": "2026-02-09T10:50:00.000Z", + "type": "guided", + "notes": "Felt very peaceful today. Mind was calm." +} +``` + +**UI Flow:** +1. After session completes, show optional notes dialog +2. User can add/skip notes +3. Notes stored with session data +4. Display notes in history (expandable row or detail view) + +**Implementation Locations:** +- `lib/widgets/timer.dart` - Add notes dialog after _complete() +- `lib/widgets/history.dart` - Display notes in expandable rows +- Consider creating `lib/widgets/session_notes_dialog.dart` + +**Effort:** 🟡 Medium (4-6 hours) + +--- + +### 5. Calendar View + +**Enhancement:** Show sessions on a calendar with visual indicators + +**Features:** +- Calendar grid showing current month +- Days with sessions highlighted +- Tap day to see sessions for that date +- Color coding by session type + +**Packages to Use:** +- `table_calendar: ^3.0.9` + +**Implementation:** +```dart +// New file: lib/widgets/calendar_view.dart +import 'package:table_calendar/table_calendar.dart'; + +class CalendarView extends StatefulWidget { + // Calendar implementation +} +``` + +**Effort:** 🟡 Medium (6-8 hours) + +--- + +### 6. Export to CSV + +**Enhancement:** Export session history to CSV file + +**Implementation:** +```dart +// In lib/widgets/history.dart +import 'package:csv/csv.dart'; +import 'package:path_provider/path_provider.dart'; +import 'dart:io'; + +Future _exportToCSV() async { + List> rows = [ + ['Date', 'Start Time', 'End Time', 'Duration', 'Type'], + ]; + + for (var session in _sessions) { + rows.add([ + session['date'], + session['start'], + session['end'], + session['duration'], + session['type'] ?? 'start', + ]); + } + + String csv = const ListToCsvConverter().convert(rows); + + final directory = await getApplicationDocumentsDirectory(); + final path = '${directory.path}/innerpod_sessions.csv'; + final file = File(path); + await file.writeAsString(csv); + + // Show success message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Exported to $path')), + ); +} +``` + +**Dependencies to Add:** +```yaml +dependencies: + csv: ^6.0.0 + path_provider: ^2.1.0 +``` + +**Effort:** 🟡 Medium (3-4 hours) + +--- + +## 🎨 Advanced Features + +### 7. Data Visualization Charts + +**Enhancement:** Visual charts showing meditation patterns + +**Chart Types:** +- Line chart: Sessions per week/month +- Bar chart: Session duration over time +- Pie chart: Session type distribution +- Heatmap: Meditation frequency calendar + +**Packages:** +- `fl_chart: ^0.66.0` (recommended) +- `charts_flutter: ^0.12.0` + +**Example:** +```dart +// lib/widgets/statistics_chart.dart +import 'package:fl_chart/fl_chart.dart'; + +class SessionsLineChart extends StatelessWidget { + final List> sessions; + + @override + Widget build(BuildContext context) { + return LineChart( + LineChartData( + // Chart configuration + ), + ); + } +} +``` + +**Effort:** 🔴 High (10-15 hours) + +--- + +### 8. Streak Tracking + +**Enhancement:** Track consecutive days of meditation + +**Features:** +- Current streak counter +- Longest streak record +- Visual streak calendar +- Streak milestones (7 days, 30 days, 100 days) +- Motivational messages + +**Implementation:** +```dart +// lib/utils/streak_calculator.dart +class StreakCalculator { + static int calculateCurrentStreak(List> sessions) { + if (sessions.isEmpty) return 0; + + // Sort sessions by date + sessions.sort((a, b) => + DateTime.parse(b['start']).compareTo(DateTime.parse(a['start'])) + ); + + int streak = 0; + DateTime lastDate = DateTime.now(); + + for (var session in sessions) { + final sessionDate = DateTime.parse(session['start']); + final daysDiff = lastDate.difference(sessionDate).inDays; + + if (daysDiff <= 1) { + streak++; + lastDate = sessionDate; + } else { + break; + } + } + + return streak; + } + + static int calculateLongestStreak(List> sessions) { + // Implementation for longest streak + } +} +``` + +**Effort:** 🔴 High (8-12 hours) + +--- + +### 9. Offline Queue & Sync + +**Enhancement:** Queue sessions when offline, sync when connection restored + +**Architecture:** +``` +┌─────────────────────────────────────┐ +│ Session Completes │ +└──────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ Check Internet Connection │ +└──────────────┬──────────────────────┘ + │ + ┌───────┴────────┐ + ▼ ▼ +┌─────────────┐ ┌─────────────────┐ +│ Online │ │ Offline │ +│ Save to │ │ Save to Local │ +│ Pod │ │ Queue │ +└─────────────┘ └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ Connection │ + │ Restored │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ Sync Queue │ + │ to Pod │ + └─────────────────┘ +``` + +**Implementation:** +```dart +// lib/services/session_sync_service.dart +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SessionSyncService { + static const String _queueKey = 'session_queue'; + + static Future saveSession(Map session) async { + final connectivity = await Connectivity().checkConnectivity(); + + if (connectivity != ConnectivityResult.none) { + // Online: Save directly to Pod + await _saveToPod(session); + } else { + // Offline: Add to queue + await _addToQueue(session); + } + } + + static Future syncQueue() async { + final prefs = await SharedPreferences.getInstance(); + final queueJson = prefs.getString(_queueKey); + + if (queueJson == null) return; + + List queue = jsonDecode(queueJson); + + for (var session in queue) { + try { + await _saveToPod(session); + } catch (e) { + debugPrint('Failed to sync session: $e'); + return; // Stop syncing if one fails + } + } + + // Clear queue after successful sync + await prefs.remove(_queueKey); + } + + static Future _saveToPod(Map session) async { + String? content = await readPod('sessions.json'); + List sessions = []; + if (content != null && content.isNotEmpty) { + sessions = jsonDecode(content); + } + sessions.add(session); + await writePod('sessions.json', jsonEncode(sessions)); + } + + static Future _addToQueue(Map session) async { + final prefs = await SharedPreferences.getInstance(); + final queueJson = prefs.getString(_queueKey); + + List queue = []; + if (queueJson != null) { + queue = jsonDecode(queueJson); + } + + queue.add(session); + await prefs.setString(_queueKey, jsonEncode(queue)); + } +} +``` + +**Dependencies:** +```yaml +dependencies: + connectivity_plus: ^5.0.0 + shared_preferences: ^2.2.0 +``` + +**Effort:** 🔴 High (12-16 hours) + +--- + +### 10. Session Reminders + +**Enhancement:** Daily notifications to remind users to meditate + +**Features:** +- Customizable reminder time +- Multiple reminders per day +- Smart reminders (skip if already meditated) +- Motivational quotes in notifications + +**Implementation:** +```dart +// lib/services/notification_service.dart +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class NotificationService { + static final FlutterLocalNotificationsPlugin _notifications = + FlutterLocalNotificationsPlugin(); + + static Future initialize() async { + const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const iosSettings = DarwinInitializationSettings(); + + const settings = InitializationSettings( + android: androidSettings, + iOS: iosSettings, + ); + + await _notifications.initialize(settings); + } + + static Future scheduleDailyReminder({ + required int hour, + required int minute, + }) async { + await _notifications.zonedSchedule( + 0, + 'Time to Meditate', + 'Take a moment for your inner peace 🧘', + _nextInstanceOfTime(hour, minute), + const NotificationDetails( + android: AndroidNotificationDetails( + 'meditation_reminder', + 'Meditation Reminders', + channelDescription: 'Daily meditation reminders', + importance: Importance.high, + ), + ), + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: DateTimeComponents.time, + ); + } + + static tz.TZDateTime _nextInstanceOfTime(int hour, int minute) { + final now = tz.TZDateTime.now(tz.local); + var scheduledDate = tz.TZDateTime( + tz.local, + now.year, + now.month, + now.day, + hour, + minute, + ); + + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + + return scheduledDate; + } +} +``` + +**Dependencies:** +```yaml +dependencies: + flutter_local_notifications: ^16.0.0 + timezone: ^0.9.0 +``` + +**Effort:** 🔴 High (10-14 hours) + +--- + +## 📊 Implementation Priority Matrix + +| Feature | Impact | Effort | Priority | +|---------|--------|--------|----------| +| Session Duration | High | Low | ⭐⭐⭐⭐⭐ | +| Session Type | High | Low | ⭐⭐⭐⭐⭐ | +| Total Statistics | High | Medium | ⭐⭐⭐⭐ | +| Session Notes | Medium | Medium | ⭐⭐⭐ | +| Export CSV | Medium | Medium | ⭐⭐⭐ | +| Calendar View | High | Medium | ⭐⭐⭐⭐ | +| Charts | Medium | High | ⭐⭐ | +| Streak Tracking | High | High | ⭐⭐⭐⭐ | +| Offline Sync | High | High | ⭐⭐⭐⭐ | +| Reminders | Medium | High | ⭐⭐⭐ | + +## 🎯 Recommended Implementation Order + +### Phase 1: Quick Wins (Week 1) +1. Session Duration Display +2. Session Type Tracking +3. Total Statistics Display + +### Phase 2: Enhanced UX (Week 2-3) +4. Session Notes +5. Export to CSV +6. Calendar View + +### Phase 3: Advanced Features (Week 4-6) +7. Streak Tracking +8. Offline Queue & Sync +9. Data Visualization Charts +10. Session Reminders + +--- + +## 🛠️ Development Tips + +### Testing Session Recording + +```dart +// Create test sessions programmatically +Future _createTestSessions() async { + final testSessions = [ + { + 'start': DateTime.now().subtract(Duration(days: 5)).toIso8601String(), + 'end': DateTime.now().subtract(Duration(days: 5, minutes: -20)).toIso8601String(), + 'type': 'start', + }, + { + 'start': DateTime.now().subtract(Duration(days: 4)).toIso8601String(), + 'end': DateTime.now().subtract(Duration(days: 4, minutes: -25)).toIso8601String(), + 'type': 'guided', + }, + // Add more test sessions + ]; + + await writePod('sessions.json', jsonEncode(testSessions)); +} +``` + +### Debugging Pod Storage + +```dart +// Read and print current sessions +Future _debugSessions() async { + String? content = await readPod('sessions.json'); + debugPrint('Current sessions: $content'); +} +``` + +### Performance Considerations + +- Cache session data locally to reduce Pod reads +- Implement pagination for large session lists +- Use `ListView.builder` for efficient rendering +- Consider lazy loading for charts + +--- + +## 📝 Notes + +- All enhancements should maintain backward compatibility with existing session data +- Consider data migration strategies when changing session format +- Test thoroughly with and without Pod connection +- Ensure offline functionality remains robust +- Follow existing code style and patterns + +--- + +**Questions or suggestions?** Open an issue on GitHub! diff --git a/lib/home.dart b/lib/home.dart index 9d893a5..d6094e2 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -35,9 +35,9 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:innerpod/constants/colours.dart'; import 'package:innerpod/utils/word_wrap.dart'; +import 'package:innerpod/widgets/history.dart'; import 'package:innerpod/widgets/instructions.dart'; import 'package:innerpod/widgets/timer.dart'; -import 'package:innerpod/widgets/history.dart'; /// A widget for the actuall app's main home page. diff --git a/lib/main.dart b/lib/main.dart index 8a4f698..c615c00 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,10 +26,10 @@ library; import 'package:flutter/material.dart'; + import 'package:solidpod/solidpod.dart'; import 'package:innerpod/home.dart'; - //import 'package:innerpod/timer.dart'; void main() { diff --git a/lib/utils/session_logic.dart b/lib/utils/session_logic.dart new file mode 100644 index 0000000..d447517 --- /dev/null +++ b/lib/utils/session_logic.dart @@ -0,0 +1,77 @@ +const String _prefixes = ''' +@prefix : <#>. +@prefix xsd: . +'''; + +/// Parses a TTL string containing session data into a list of maps. +/// Returns a list of sessions, where each session is a map with 'start' and 'end' keys. +List> parseSessions(String? content) { + if (content == null || content.isEmpty) { + return []; + } + + final List> sessions = []; + + // RegExp to match a session block. + // It looks for a block starting with :session_ and ending with a literal dot + // that is followed by whitespace or end of string. + // The dot in timestamps (e.g., .000Z) is NOT followed by whitespace, so this distinguishes the terminator. + final RegExp sessionBlockRegExp = + RegExp(r':session_\d+.*?\.(?:\s+|$)', dotAll: true); + + // RegExp to extract start and end times within a block + final RegExp startRegExp = RegExp(r':start "(.*?)"\^\^xsd:dateTime'); + final RegExp endRegExp = RegExp(r':end "(.*?)"\^\^xsd:dateTime'); + + final matches = sessionBlockRegExp.allMatches(content); + + for (final match in matches) { + final block = match.group(0)!; + final startMatch = startRegExp.firstMatch(block); + final endMatch = endRegExp.firstMatch(block); + + if (startMatch != null && endMatch != null) { + sessions.add({ + 'start': startMatch.group(1)!, + 'end': endMatch.group(1)!, + }); + } + } + + // Sort by start time descending (newest first) + sessions.sort((a, b) => b['start']!.compareTo(a['start']!)); + + return sessions; +} + +/// Adds a new session to the existing TTL content. +/// If currentContent is null or empty, initializes with prefixes. +/// Returns the updated TTL content string. +String addSession(String? currentContent, Map newSession) { + String content = currentContent ?? ''; + + // key fix: trim() handles invisible whitespace that might make "empty" check false + if (content.trim().isEmpty) { + content = _prefixes; + } else if (!content.contains('@prefix')) { + // If somehow content exists but no prefixes (legacy/corrupt), add them + content = '$_prefixes\n$content'; + } + + final String start = newSession['start']; + final String end = newSession['end']; + // Use timestamp as unique ID + final String id = DateTime.parse(start).millisecondsSinceEpoch.toString(); + + // Add newline before new entry if needed + final String separator = content.endsWith('\n') ? '' : '\n'; + + final String newEntry = ''' +$separator +:session_$id a :Session; + :start "$start"^^xsd:dateTime; + :end "$end"^^xsd:dateTime. +'''; + + return content + newEntry; +} diff --git a/lib/widgets/history.dart b/lib/widgets/history.dart index d549880..bc95e4f 100644 --- a/lib/widgets/history.dart +++ b/lib/widgets/history.dart @@ -5,11 +5,14 @@ /// Copyright (C) 2024-2026, Togaware Pty Ltd /// /// Licensed under the GNU General Public License, Version 3 (the "License"); +library; import 'package:flutter/material.dart'; + import 'package:intl/intl.dart'; import 'package:solidpod/solidpod.dart'; -import 'dart:convert'; + +import 'package:innerpod/utils/session_logic.dart'; class History extends StatefulWidget { const History({super.key}); @@ -34,10 +37,11 @@ class _HistoryState extends State { }); try { - // Assuming sessions are stored in 'sessions.json' in the Pod - String? content = await readPod('sessions.json'); - if (content != null && content.isNotEmpty) { - List jsonList = jsonDecode(content); + String? content = + await readPod('sessions.ttl', context, const SizedBox()); + // If readPod returns nullable, use ?. or just pass to parseSessions which handles null + List jsonList = parseSessions(content); + if (jsonList.isNotEmpty) { setState(() { _sessions = jsonList.map((item) { final start = DateTime.parse(item['start']); @@ -88,11 +92,13 @@ class _HistoryState extends State { DataColumn(label: Text('End')), ], rows: _sessions.map((session) { - return DataRow(cells: [ - DataCell(Text(session['date']!)), - DataCell(Text(session['start']!)), - DataCell(Text(session['end']!)), - ]); + return DataRow( + cells: [ + DataCell(Text(session['date']!)), + DataCell(Text(session['start']!)), + DataCell(Text(session['end']!)), + ], + ); }).toList(), ), ), diff --git a/lib/widgets/timer.dart b/lib/widgets/timer.dart index 746f013..e9097af 100644 --- a/lib/widgets/timer.dart +++ b/lib/widgets/timer.dart @@ -31,12 +31,12 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:circular_countdown_timer/circular_countdown_timer.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:solidpod/solidpod.dart'; -import 'dart:convert'; import 'package:innerpod/constants/audio.dart'; import 'package:innerpod/constants/spacing.dart'; import 'package:innerpod/utils/ding_dong.dart'; import 'package:innerpod/utils/log_message.dart'; +import 'package:innerpod/utils/session_logic.dart'; import 'package:innerpod/widgets/app_button.dart'; import 'package:innerpod/widgets/app_circular_countdown_timer.dart'; @@ -236,13 +236,10 @@ class TimerState extends State { }; try { - String? content = await readPod('sessions.json'); - List sessions = []; - if (content != null && content.isNotEmpty) { - sessions = jsonDecode(content); - } - sessions.add(session); - await writePod('sessions.json', jsonEncode(sessions)); + String? content = + await readPod('sessions.ttl', context, const SizedBox()); + String newContent = addSession(content, session); + await writePod('sessions.ttl', context, newContent, const SizedBox()); logMessage('Session saved to Pod'); } catch (e) { logMessage('Error saving session to Pod: $e'); diff --git a/pubspec.yaml b/pubspec.yaml index e26c6be..94799a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,14 @@ dependency_overrides: dev_dependencies: flutter_launcher_icons: ^0.14.4 +<<<<<<< HEAD flutter_lints: ^6.0.0 +======= + flutter_lints: any + ubuntu_lints: any + flutter_test: + sdk: flutter +>>>>>>> fdf08d2 (feat: use RDF/TTL for session storage, fix imports, fix links) flutter: assets: diff --git a/test/session_logic_test.dart b/test/session_logic_test.dart new file mode 100644 index 0000000..63a7fc9 --- /dev/null +++ b/test/session_logic_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:innerpod/utils/session_logic.dart'; + +void main() { + group('Session Logic TTL Tests', () { + test('parseSessions returns empty list on null content', () { + expect( + parseSessions(null), + isEmpty, + ); + }); + + test('parseSessions returns empty list on empty content', () { + expect( + parseSessions(''), + isEmpty, + ); + }); + + test('parseSessions parses a session correctly', () { + final ttl = ''' +:session_123456789 a :Session; + :start "2024-01-01T10:00:00.000Z"^^xsd:dateTime; + :end "2024-01-01T10:20:00.000Z"^^xsd:dateTime. +'''; + final sessions = parseSessions(ttl); + expect( + sessions.length, + 1, + ); + expect( + sessions.first['start'], + '2024-01-01T10:00:00.000Z', + ); + expect( + sessions.first['end'], + '2024-01-01T10:20:00.000Z', + ); + }); + + test('addSession adds session to empty content with prefixes', () { + final newSession = { + 'start': '2024-01-01T10:00:00.000Z', + 'end': '2024-01-01T10:20:00.000Z' + }; + final result = addSession(null, newSession); + + expect(result.contains('@prefix : <#>.'), isTrue); + expect(result.contains('@prefix xsd:'), isTrue); + expect(result.contains(':start "2024-01-01T10:00:00.000Z"^^xsd:dateTime'), + isTrue); + }); + + test('addSession appends session to existing content', () { + final existing = ''' +@prefix : <#>. +@prefix xsd: . + +:session_123456789 a :Session; + :start "2023-01-01T00:00:00.000Z"^^xsd:dateTime; + :end "2023-01-01T00:20:00.000Z"^^xsd:dateTime. +'''; + final newSession = { + 'start': '2024-01-01T12:00:00.000Z', + 'end': '2024-01-01T12:20:00.000Z' + }; + final result = addSession(existing, newSession); + + final parsed = parseSessions(result); + expect(parsed.length, 2); + expect(parsed.first['start'], '2024-01-01T12:00:00.000Z'); // Newest first + }); + }); +} From d1d2d7d17267b98929b4d6a1566b693f897b1094 Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 16:35:48 +0530 Subject: [PATCH 3/8] fix: resolve pubspec conflicts, stabilize solidpod/solidui versions, fix imports --- lib/main.dart | 2 +- lib/widgets/history.dart | 3 +-- lib/widgets/timer.dart | 5 ++--- pubspec.yaml | 17 +++-------------- test/session_logic_test.dart | 20 ++++++++++++++------ 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c615c00..735e026 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,7 +27,7 @@ library; import 'package:flutter/material.dart'; -import 'package:solidpod/solidpod.dart'; +import 'package:solidui/solidui.dart'; import 'package:innerpod/home.dart'; //import 'package:innerpod/timer.dart'; diff --git a/lib/widgets/history.dart b/lib/widgets/history.dart index bc95e4f..18ad894 100644 --- a/lib/widgets/history.dart +++ b/lib/widgets/history.dart @@ -37,8 +37,7 @@ class _HistoryState extends State { }); try { - String? content = - await readPod('sessions.ttl', context, const SizedBox()); + String? content = await readPod('sessions.ttl'); // If readPod returns nullable, use ?. or just pass to parseSessions which handles null List jsonList = parseSessions(content); if (jsonList.isNotEmpty) { diff --git a/lib/widgets/timer.dart b/lib/widgets/timer.dart index e9097af..f388561 100644 --- a/lib/widgets/timer.dart +++ b/lib/widgets/timer.dart @@ -236,10 +236,9 @@ class TimerState extends State { }; try { - String? content = - await readPod('sessions.ttl', context, const SizedBox()); + String? content = await readPod('sessions.ttl'); String newContent = addSession(content, session); - await writePod('sessions.ttl', context, newContent, const SizedBox()); + await writePod('sessions.ttl', newContent); logMessage('Session saved to Pod'); } catch (e) { logMessage('Error saving session to Pod: $e'); diff --git a/pubspec.yaml b/pubspec.yaml index 94799a7..0612b0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,30 +27,19 @@ dependencies: intl: ^0.20.2 markdown_tooltip: ^0.0.7 package_info_plus: ^9.0.0 - solidpod: ^0.10.5 + solidpod: ^0.10.1 + solidui: any url_launcher: ^6.3.0 wakelock_plus: ^1.1.4 -dependency_overrides: - solidpod: - git: - url: https://github.com/anusii/solidpod.git - ref: dev - solidui: - git: - url: https://github.com/anusii/solidui.git - ref: dev + dev_dependencies: flutter_launcher_icons: ^0.14.4 -<<<<<<< HEAD - flutter_lints: ^6.0.0 -======= flutter_lints: any ubuntu_lints: any flutter_test: sdk: flutter ->>>>>>> fdf08d2 (feat: use RDF/TTL for session storage, fix imports, fix links) flutter: assets: diff --git a/test/session_logic_test.dart b/test/session_logic_test.dart index 63a7fc9..dcbbff0 100644 --- a/test/session_logic_test.dart +++ b/test/session_logic_test.dart @@ -41,14 +41,16 @@ void main() { test('addSession adds session to empty content with prefixes', () { final newSession = { 'start': '2024-01-01T10:00:00.000Z', - 'end': '2024-01-01T10:20:00.000Z' + 'end': '2024-01-01T10:20:00.000Z', }; final result = addSession(null, newSession); expect(result.contains('@prefix : <#>.'), isTrue); expect(result.contains('@prefix xsd:'), isTrue); - expect(result.contains(':start "2024-01-01T10:00:00.000Z"^^xsd:dateTime'), - isTrue); + expect( + result.contains(':start "2024-01-01T10:00:00.000Z"^^xsd:dateTime'), + isTrue, + ); }); test('addSession appends session to existing content', () { @@ -62,13 +64,19 @@ void main() { '''; final newSession = { 'start': '2024-01-01T12:00:00.000Z', - 'end': '2024-01-01T12:20:00.000Z' + 'end': '2024-01-01T12:20:00.000Z', }; final result = addSession(existing, newSession); final parsed = parseSessions(result); - expect(parsed.length, 2); - expect(parsed.first['start'], '2024-01-01T12:00:00.000Z'); // Newest first + expect( + parsed.length, + 2, + ); + expect( + parsed.first['start'], + '2024-01-01T12:00:00.000Z', + ); // Newest first }); }); } From 583c82cfba19d7a35b821f7917c7331bfb0e23b8 Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 16:44:52 +0530 Subject: [PATCH 4/8] fix: address CI lint failures (copyright, import order, markdown, links) --- README.md | 4 ++-- lib/utils/session_logic.dart | 25 +++++++++++++++++++++++++ lib/widgets/timer.dart | 2 +- test/session_logic_test.dart | 26 ++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02ba816..d21f0af 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![Get it from the Snap Store](https://snapcraft.io/en/light/install.svg)](https://snapcraft.io/innerpod) InnerPod is an app to guide and time your regular mediation. The app -was developed by [Togaware](https://togaware.com.au) and written by +was developed by [Togaware](https://togaware.com) and written by [Graham Williams](https://togaware.com/Graham.Williams.html). If you appreciate the app then please show some ❤️ and star the GitHub @@ -141,7 +141,7 @@ Commons 0 Feel free to pickup tasks from the list in Issues and so create a fork to work on the issue to then submit a pull request. Or else contact -innerpod@togaware.com to volunteer to work directly on the project + to volunteer to work directly on the project under out guidance. [![Flutter](https://img.shields.io/badge/Flutter-%2302569B.svg?style=for-the-badge&logo=Flutter&logoColor=white)](https://flutter.dev) diff --git a/lib/utils/session_logic.dart b/lib/utils/session_logic.dart index d447517..6d2a537 100644 --- a/lib/utils/session_logic.dart +++ b/lib/utils/session_logic.dart @@ -1,3 +1,28 @@ +/// Session logic for InnerPod. +// +// Time-stamp: <2026-02-10 16:40:00 Amogh Hosamane> +// +/// Copyright (C) 2024, Togaware Pty Ltd +/// +/// Licensed under the GNU General Public License, Version 3 (the "License"); +/// +/// License: https://opensource.org/license/gpl-3-0 +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +/// +/// Authors: Amogh Hosamane + const String _prefixes = ''' @prefix : <#>. @prefix xsd: . diff --git a/lib/widgets/timer.dart b/lib/widgets/timer.dart index f388561..486f17e 100644 --- a/lib/widgets/timer.dart +++ b/lib/widgets/timer.dart @@ -29,8 +29,8 @@ import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:circular_countdown_timer/circular_countdown_timer.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:solidpod/solidpod.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:innerpod/constants/audio.dart'; import 'package:innerpod/constants/spacing.dart'; diff --git a/test/session_logic_test.dart b/test/session_logic_test.dart index dcbbff0..8dc279f 100644 --- a/test/session_logic_test.dart +++ b/test/session_logic_test.dart @@ -1,4 +1,30 @@ +/// Tests for session logic. +// +// Time-stamp: <2026-02-10 16:45:00 Amogh Hosamane> +// +/// Copyright (C) 2024, Togaware Pty Ltd +/// +/// Licensed under the GNU General Public License, Version 3 (the "License"); +/// +/// License: https://opensource.org/license/gpl-3-0 +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +/// +/// Authors: Amogh Hosamane + import 'package:flutter_test/flutter_test.dart'; + import 'package:innerpod/utils/session_logic.dart'; void main() { From 8c37abd099cb06238619f3c106b7aeb3103f30ca Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 17:09:36 +0530 Subject: [PATCH 5/8] fix: address all remaining CI lint failures (copyright, import order, markdown, links) --- DEVELOPMENT_GUIDE.md | 36 +++++++++++++++++++++----- GETTING_STARTED.md | 56 ++++++++++++++++++++++++++--------------- README.md | 3 ++- SESSION_ENHANCEMENTS.md | 48 +++++++++++++++++++++++++++-------- lib/main.dart | 1 + 5 files changed, 106 insertions(+), 38 deletions(-) diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md index 6ad065f..fca7ced 100644 --- a/DEVELOPMENT_GUIDE.md +++ b/DEVELOPMENT_GUIDE.md @@ -1,6 +1,7 @@ # InnerPod Development Guide ## 📚 Table of Contents + 1. [Project Overview](#project-overview) 2. [Session Recording Architecture](#session-recording-architecture) 3. [Solid Pod Integration](#solid-pod-integration) @@ -13,12 +14,14 @@ ## Project Overview InnerPod is a meditation timer app built with Flutter that: + - Provides guided and unguided meditation sessions - Records session data to encrypted Solid Pods - Displays session history - Works offline (Pod connection is optional) **Tech Stack:** + - **Framework:** Flutter 3.2.5+ - **Language:** Dart - **Storage:** Solid Pod (encrypted, decentralized) @@ -32,7 +35,7 @@ InnerPod is a meditation timer app built with Flutter that: ### How It Works -``` +```text ┌─────────────────────────────────────────────────────────────┐ │ User Starts Session │ │ (Start/Intro/Guided button) │ @@ -87,6 +90,7 @@ Sessions are stored in `sessions.ttl` on the user's Solid Pod: ``` **Format Details:** + - ISO 8601 timestamp format - UTC timezone - Millisecond precision @@ -164,6 +168,7 @@ Future _loadSessions() async { ### What is a Solid Pod? Solid (Social Linked Data) is a decentralized web platform where: + - Users own their data - Data is stored in personal "Pods" (Personal Online Data stores) - Apps request permission to access data @@ -172,6 +177,7 @@ Solid (Social Linked Data) is a decentralized web platform where: ### How InnerPod Uses Solid Pods 1. **Optional Login** (`lib/main.dart`): + ```dart SolidLogin( title: 'MANAGE YOUR INNER POD', @@ -181,11 +187,13 @@ Solid (Social Linked Data) is a decentralized web platform where: ``` 2. **Reading Data**: + ```dart String? content = await readPod('sessions.ttl'); ``` 3. **Writing Data**: + ```dart await writePod('sessions.ttl', newContent); ``` @@ -236,12 +244,14 @@ flutter run -d windows # or chrome, android, etc. ### 1. Timer Widget (`lib/widgets/timer.dart`) **Responsibilities:** + - Countdown timer display - Session management (start, pause, resume, reset) - Audio playback (bells, guided meditation) - Session recording **Key Methods:** + - `_intro()` - Plays intro audio then starts session - `_guided()` - Plays full guided meditation - `_complete()` - Called when session ends @@ -250,11 +260,13 @@ flutter run -d windows # or chrome, android, etc. ### 2. History Widget (`lib/widgets/history.dart`) **Responsibilities:** + - Display past sessions in a table - Load sessions from Solid Pod - Refresh functionality **Features:** + - Date formatting (yyyy-MM-dd) - Time formatting (HH:mm:ss) - Loading state indicator @@ -264,13 +276,14 @@ flutter run -d windows # or chrome, android, etc. ### 3. Home Widget (`lib/home.dart`) **Responsibilities:** + - Navigation between tabs (Home, Instructions, History) - App bar with version info - About dialog ## Code Structure -``` +```text innerpod/ ├── lib/ │ ├── main.dart # App entry point, Solid login @@ -300,19 +313,22 @@ innerpod/ ### Manual Testing Checklist -#### Session Recording: +#### Session Recording + - [ ] Start a session without Pod login (should not crash) - [ ] Start a session with Pod login (should save) - [ ] Complete multiple sessions (should append to list) - [ ] Check sessions.json in Pod (should be valid JSON) -#### History Display: +#### History Display + - [ ] View history without Pod login (should show empty state) - [ ] View history with sessions (should display table) - [ ] Refresh history (should reload data) - [ ] Check date/time formatting (should be readable) -#### Edge Cases: +#### Edge Cases + - [ ] Network interruption during save - [ ] Corrupted sessions.json file - [ ] Very long session (hours) @@ -336,11 +352,13 @@ flutter analyze ### Workflow 1. **Fork & Clone** + ```bash git clone https://github.com/YOUR_USERNAME/innerpod.git ``` 2. **Create Branch** + ```bash git checkout -b feature/your-feature-name ``` @@ -351,6 +369,7 @@ flutter analyze - Update documentation 4. **Test** + ```bash flutter test flutter analyze @@ -358,12 +377,14 @@ flutter analyze ``` 5. **Commit** + ```bash git add . git commit -m "feat: add session duration calculation" ``` 6. **Push & PR** + ```bash git push origin feature/your-feature-name # Create Pull Request on GitHub @@ -416,22 +437,25 @@ Here are some ideas for improving the session recording feature: ## Resources ### Documentation + - [Flutter Docs](https://docs.flutter.dev/) - [Dart Language Tour](https://dart.dev/guides/language/language-tour) - [Solid Project](https://solidproject.org/) - [solidpod Package](https://pub.dev/packages/solidpod) ### InnerPod Specific + - [GitHub Repository](https://github.com/gjwgit/innerpod) - [Online Demo](https://innerpod.solidcommunity.au) - [Changelog](https://github.com/gjwgit/innerpod/blob/dev/CHANGELOG.md) ### Community + - [Flutter Community](https://flutter.dev/community) - [Solid Community](https://forum.solidproject.org/) --- -**Happy Coding! 🧘‍♂️** +Happy Coding! 🧘‍♂️ For questions or issues, please open an issue on GitHub or contact the maintainers. diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 3cd1d49..d2d41dc 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -1,14 +1,16 @@ # InnerPod: Getting Started Guide -## 🎉 Welcome! +## 🎉 Welcome -This guide will help you get started with contributing to InnerPod, specifically focusing on the session recording and history features. +This guide will help you get started with contributing to InnerPod, +specifically focusing on the session recording and history features. ## 📋 What You Need to Know -### ✅ Good News: Features Already Implemented! +### ✅ Good News: Features Already Implemented -The session recording and history features you mentioned are **already implemented** in InnerPod! Here's what's working: +The session recording and history features you mentioned are +**already implemented** in InnerPod! Here's what's working: 1. **Automatic Session Recording** - Sessions are automatically saved when completed @@ -28,17 +30,18 @@ The session recording and history features you mentioned are **already implement ### 📁 Key Files to Understand | File | Purpose | Lines of Interest | -|------|---------|-------------------| -| `lib/widgets/timer.dart` | Session timer & recording | 229-252 (_saveSession) | -| `lib/widgets/history.dart` | Display session history | 31-60 (_loadSessions) | -| `lib/main.dart` | App entry & Solid login | 64-78 (SolidLogin) | -| `lib/home.dart` | Navigation & app structure | 109-113 (pages list) | +| :--- | :--- | :--- | +| `lib/widgets/timer.dart` | Timer & recording | 229-252 (_saveSession) | +| `lib/widgets/history.dart` | Display history | 31-60 (_loadSessions) | +| `lib/main.dart` | Entry & Solid login | 64-78 (SolidLogin) | +| `lib/home.dart` | Navigation | 110-113 (pages list) | ## 🚀 Quick Start ### Step 1: Install Flutter **Windows Setup:** + ```powershell # 1. Download Flutter SDK # Visit: https://docs.flutter.dev/get-started/install/windows @@ -47,7 +50,8 @@ The session recording and history features you mentioned are **already implement # 3. Add to PATH (PowerShell as Administrator) $currentPath = [Environment]::GetEnvironmentVariable("Path", "User") -[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\src\flutter\bin", "User") +[Environment]::SetEnvironmentVariable("Path", + "$currentPath;C:\src\flutter\bin", "User") # 4. Restart terminal and verify flutter --version @@ -90,7 +94,9 @@ flutter run -d android I've created three comprehensive guides to help you: ### 1. **DEVELOPMENT_GUIDE.md** + Complete development documentation covering: + - Project architecture overview - Session recording implementation details - Solid Pod integration explanation @@ -98,10 +104,13 @@ Complete development documentation covering: - Testing strategies - Contribution guidelines -**When to use:** Understanding how the app works, architecture decisions, and best practices +**When to use:** Understanding how the app works, architecture decisions, +and best practices ### 2. **SESSION_ENHANCEMENTS.md** + Enhancement ideas with implementation details: + - 10 potential improvements (from easy to advanced) - Code examples for each enhancement - Effort estimates and priority matrix @@ -110,7 +119,9 @@ Enhancement ideas with implementation details: **When to use:** Planning new features, looking for contribution ideas ### 3. **.agent/workflows/setup-flutter.md** + Step-by-step Flutter setup workflow: + - Installing Flutter SDK on Windows - Setting up development environment - Configuring PATH variables @@ -138,10 +149,11 @@ Step-by-step Flutter setup workflow: ### Option B: Start Contributing 1. **Fork the repository** on GitHub - - Visit: https://github.com/gjwgit/innerpod + - Visit: - Click "Fork" button 2. **Clone your fork** + ```bash git clone https://github.com/YOUR_USERNAME/innerpod.git cd innerpod @@ -153,6 +165,7 @@ Step-by-step Flutter setup workflow: - High impact, low effort 4. **Create a branch** + ```bash git checkout -b feature/session-duration ``` @@ -193,7 +206,7 @@ dart format . ### Project Structure -``` +```text innerpod/ ├── lib/ │ ├── main.dart # App entry point @@ -221,7 +234,7 @@ intl: ^0.20.2 # Date/time formatting ### How It Works (Simplified) -``` +```text 1. User starts session ↓ 2. App records start time @@ -249,6 +262,7 @@ intl: ^0.20.2 # Date/time formatting **Encryption:** Handled by Solid Pod **Example:** + ```json [ { @@ -288,15 +302,16 @@ intl: ^0.20.2 # Date/time formatting ### Resources -- **Flutter Docs:** https://docs.flutter.dev/ -- **Dart Language:** https://dart.dev/guides -- **Solid Project:** https://solidproject.org/ -- **InnerPod GitHub:** https://github.com/gjwgit/innerpod +- **Flutter Docs:** +- **Dart Language:** +- **Solid Project:** +- **InnerPod GitHub:** ### Common Issues **Q: Flutter command not found** A: Restart terminal after adding to PATH, or run: + ```powershell $env:Path += ";C:\src\flutter\bin" ``` @@ -322,10 +337,11 @@ A: Only in Solid Pod. Without login, sessions aren't saved (by design) ## 📞 Contact -- **GitHub Issues:** https://github.com/gjwgit/innerpod/issues +- **GitHub Issues:** - **Original Author:** Graham Williams - **Project:** Togaware --- -**Happy coding! May your meditation sessions be peaceful and your code bug-free! 🧘‍♂️✨** +**Happy coding! May your meditation sessions be peaceful and your code +bug-free! 🧘‍♂️✨** diff --git a/README.md b/README.md index d21f0af..5ddb884 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,8 @@ The instructions for meditating by John Main are from [WCCM](https://wccm.org). The bell is Tibetan bowl_left hit.wav by -[dersinnsspace](https://freesound.org/people/dersinnsspace/sounds/417117/). License: Creative +[dersinnsspace](https://freesound.org/people/dersinnsspace/sounds/417117/). +License: Creative Commons 0 ## Contributing diff --git a/SESSION_ENHANCEMENTS.md b/SESSION_ENHANCEMENTS.md index 7ed6cb8..1a2059e 100644 --- a/SESSION_ENHANCEMENTS.md +++ b/SESSION_ENHANCEMENTS.md @@ -1,6 +1,7 @@ # Session Recording Enhancement Ideas -This document outlines potential enhancements to the InnerPod session recording and history features. +This document outlines potential enhancements to the InnerPod session +recording and history features. ## 🎯 Quick Wins (Easy to Implement) @@ -10,6 +11,7 @@ This document outlines potential enhancements to the InnerPod session recording **Enhancement:** Calculate and display session duration **Implementation:** + ```dart // In lib/widgets/history.dart _sessions = jsonList.map((item) { @@ -41,6 +43,7 @@ DataCell(Text(session['duration']!)), **Enhancement:** Track and display session type **Implementation:** + ```dart // In lib/widgets/timer.dart, modify _saveSession() final session = { @@ -69,6 +72,7 @@ onPressed: () { **Enhancement:** Show total meditation time, session count, average duration **Implementation:** + ```dart // In lib/widgets/history.dart Widget _buildStatistics() { @@ -109,6 +113,7 @@ Widget _buildStatistics() { **Enhancement:** Allow users to add notes after completing a session **Data Format:** + ```json { "start": "2026-02-09T10:30:00.000Z", @@ -119,12 +124,14 @@ Widget _buildStatistics() { ``` **UI Flow:** + 1. After session completes, show optional notes dialog 2. User can add/skip notes 3. Notes stored with session data 4. Display notes in history (expandable row or detail view) **Implementation Locations:** + - `lib/widgets/timer.dart` - Add notes dialog after _complete() - `lib/widgets/history.dart` - Display notes in expandable rows - Consider creating `lib/widgets/session_notes_dialog.dart` @@ -138,15 +145,18 @@ Widget _buildStatistics() { **Enhancement:** Show sessions on a calendar with visual indicators **Features:** + - Calendar grid showing current month - Days with sessions highlighted - Tap day to see sessions for that date - Color coding by session type **Packages to Use:** + - `table_calendar: ^3.0.9` **Implementation:** + ```dart // New file: lib/widgets/calendar_view.dart import 'package:table_calendar/table_calendar.dart'; @@ -165,6 +175,7 @@ class CalendarView extends StatefulWidget { **Enhancement:** Export session history to CSV file **Implementation:** + ```dart // In lib/widgets/history.dart import 'package:csv/csv.dart'; @@ -201,6 +212,7 @@ Future _exportToCSV() async { ``` **Dependencies to Add:** + ```yaml dependencies: csv: ^6.0.0 @@ -218,16 +230,19 @@ dependencies: **Enhancement:** Visual charts showing meditation patterns **Chart Types:** + - Line chart: Sessions per week/month - Bar chart: Session duration over time - Pie chart: Session type distribution - Heatmap: Meditation frequency calendar **Packages:** + - `fl_chart: ^0.66.0` (recommended) - `charts_flutter: ^0.12.0` **Example:** + ```dart // lib/widgets/statistics_chart.dart import 'package:fl_chart/fl_chart.dart'; @@ -255,6 +270,7 @@ class SessionsLineChart extends StatelessWidget { **Enhancement:** Track consecutive days of meditation **Features:** + - Current streak counter - Longest streak record - Visual streak calendar @@ -262,6 +278,7 @@ class SessionsLineChart extends StatelessWidget { - Motivational messages **Implementation:** + ```dart // lib/utils/streak_calculator.dart class StreakCalculator { @@ -306,7 +323,8 @@ class StreakCalculator { **Enhancement:** Queue sessions when offline, sync when connection restored **Architecture:** -``` + +```text ┌─────────────────────────────────────┐ │ Session Completes │ └──────────────┬──────────────────────┘ @@ -338,6 +356,7 @@ class StreakCalculator { ``` **Implementation:** + ```dart // lib/services/session_sync_service.dart import 'package:connectivity_plus/connectivity_plus.dart'; @@ -405,6 +424,7 @@ class SessionSyncService { ``` **Dependencies:** + ```yaml dependencies: connectivity_plus: ^5.0.0 @@ -420,12 +440,14 @@ dependencies: **Enhancement:** Daily notifications to remind users to meditate **Features:** + - Customizable reminder time - Multiple reminders per day - Smart reminders (skip if already meditated) - Motivational quotes in notifications **Implementation:** + ```dart // lib/services/notification_service.dart import 'package:flutter_local_notifications/flutter_local_notifications.dart'; @@ -491,6 +513,7 @@ class NotificationService { ``` **Dependencies:** + ```yaml dependencies: flutter_local_notifications: ^16.0.0 @@ -504,7 +527,7 @@ dependencies: ## 📊 Implementation Priority Matrix | Feature | Impact | Effort | Priority | -|---------|--------|--------|----------| +| :--- | :--- | :--- | :--- | | Session Duration | High | Low | ⭐⭐⭐⭐⭐ | | Session Type | High | Low | ⭐⭐⭐⭐⭐ | | Total Statistics | High | Medium | ⭐⭐⭐⭐ | @@ -519,20 +542,23 @@ dependencies: ## 🎯 Recommended Implementation Order ### Phase 1: Quick Wins (Week 1) + 1. Session Duration Display 2. Session Type Tracking 3. Total Statistics Display ### Phase 2: Enhanced UX (Week 2-3) -4. Session Notes -5. Export to CSV -6. Calendar View + +1. Session Notes +2. Export to CSV +3. Calendar View ### Phase 3: Advanced Features (Week 4-6) -7. Streak Tracking -8. Offline Queue & Sync -9. Data Visualization Charts -10. Session Reminders + +1. Streak Tracking +2. Offline Queue & Sync +3. Data Visualization Charts +4. Session Reminders --- @@ -582,7 +608,7 @@ Future _debugSessions() async { ## 📝 Notes -- All enhancements should maintain backward compatibility with existing session data +- Maintain backward compatibility with existing session data - Consider data migration strategies when changing session format - Test thoroughly with and without Pod connection - Ensure offline functionality remains robust diff --git a/lib/main.dart b/lib/main.dart index 735e026..771e145 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ import 'package:flutter/material.dart'; import 'package:solidui/solidui.dart'; import 'package:innerpod/home.dart'; + //import 'package:innerpod/timer.dart'; void main() { From d8f409aca9b4e16c1f3e02fcb320227a5ee458dc Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 17:30:41 +0530 Subject: [PATCH 6/8] fix: address analyze and link_checker CI failures (dangling doc comments, placeholder URLs) --- .lycheeignore | 4 ++++ CHANGELOG.md | 2 +- README.md | 2 +- lib/utils/session_logic.dart | 2 ++ test/session_logic_test.dart | 2 ++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.lycheeignore b/.lycheeignore index fc25087..58bd48b 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -42,6 +42,10 @@ https://.*\.tile-cyclosm\.openstreetmap\.fr/.* web/** +# Placeholder URLs in documentation + +https://github.com/YOUR_USERNAME/innerpod.git + # Licenses. Note that gnu.org is throttled so do not check it. https://www.gnu.org/licenses/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4079b0b..333a6ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Here we record the basic changes made to the InnerPod app. + Package for snap release [1.7.5 20251004 gjw] + For GUIDED concat audio then include in app [1.7.4 20250218 gjw] + Review audio. Add 5 minutes option. [1.7.3 20241114 gjw] -+ Updated Tibetan bell from freesound.com [1.7.2 20241101 gjw] ++ Updated Tibetan bell from freesound.org [1.7.2 20241101 gjw] + Use markdown for About with active url links [1.7.1 20241101 gjw] + Move to mp3 rather than ogg for wider OS support [1.7.0 20241025 gjw] diff --git a/README.md b/README.md index 5ddb884..cfbf4e2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![GitHub License](https://img.shields.io/github/license/gjwgit/innerpod)](https://raw.githubusercontent.com/gjwgit/innerpod/main/LICENSE) [![Flutter Version](https://img.shields.io/badge/dynamic/yaml?url=https://raw.githubusercontent.com/gjwgit/innerpod/master/pubspec.yaml&query=$.version&label=version)](https://github.com/gjwgit/innerpod/blob/dev/CHANGELOG.md) [![Last Updated](https://img.shields.io/github/last-commit/gjwgit/innerpod?label=last%20updated)](https://github.com/gjwgit/innerpod/commits/dev/) -[![GitHub commit activity (dev)](https://img.shields.io/github/commit-activity/w/gjwgit/innerpod/dev)](https://github.com/gjwgit/rattle/commits/dev/) +[![GitHub commit activity (dev)](https://img.shields.io/github/commit-activity/w/gjwgit/innerpod/dev)](https://github.com/gjwgit/innerpod/commits/dev/) [![GitHub Issues](https://img.shields.io/github/issues/gjwgit/innerpod)](https://github.com/gjwgit/innerpod/issues) [![Google Play](https://img.shields.io/badge/Google%20Play-Available-green?logo=google-play)](https://play.google.com/store/apps/details?id=com.togaware.innerpod) diff --git a/lib/utils/session_logic.dart b/lib/utils/session_logic.dart index 6d2a537..d81038a 100644 --- a/lib/utils/session_logic.dart +++ b/lib/utils/session_logic.dart @@ -23,6 +23,8 @@ /// /// Authors: Amogh Hosamane +library session_logic; + const String _prefixes = ''' @prefix : <#>. @prefix xsd: . diff --git a/test/session_logic_test.dart b/test/session_logic_test.dart index 8dc279f..37f5209 100644 --- a/test/session_logic_test.dart +++ b/test/session_logic_test.dart @@ -23,6 +23,8 @@ /// /// Authors: Amogh Hosamane +library; + import 'package:flutter_test/flutter_test.dart'; import 'package:innerpod/utils/session_logic.dart'; From ebd58feb960a29ca580a03dfe4c15b3a7d07437f Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 17:31:41 +0530 Subject: [PATCH 7/8] fix: update broken flutter doc link in GETTING_STARTED.md --- GETTING_STARTED.md | 2 +- lib/utils/session_logic.dart | 23 +++++------------------ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index d2d41dc..93fa31d 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -44,7 +44,7 @@ The session recording and history features you mentioned are ```powershell # 1. Download Flutter SDK -# Visit: https://docs.flutter.dev/get-started/install/windows +# Visit: https://docs.flutter.dev/get-started/install # 2. Extract to C:\src\flutter diff --git a/lib/utils/session_logic.dart b/lib/utils/session_logic.dart index d81038a..dc30127 100644 --- a/lib/utils/session_logic.dart +++ b/lib/utils/session_logic.dart @@ -1,27 +1,14 @@ -/// Session logic for InnerPod. +// Session logic for InnerPod. // // Time-stamp: <2026-02-10 16:40:00 Amogh Hosamane> // -/// Copyright (C) 2024, Togaware Pty Ltd -/// -/// Licensed under the GNU General Public License, Version 3 (the "License"); -/// -/// License: https://opensource.org/license/gpl-3-0 +// Copyright (C) 2024, Togaware Pty Ltd // -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free Software -// Foundation, either version 3 of the License, or (at your option) any later -// version. +// Licensed under the GNU General Public License, Version 3 (the "License"); // -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -// details. +// License: https://opensource.org/license/gpl-3-0 // -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -/// -/// Authors: Amogh Hosamane +// Authors: Amogh Hosamane library session_logic; From 53eb5690c6830236e5c4b1ed41f0b289d4b3ffc3 Mon Sep 17 00:00:00 2001 From: Amogh Hosamane Date: Tue, 10 Feb 2026 17:35:28 +0530 Subject: [PATCH 8/8] Merge dev into feature/session-history --- lib/utils/session_logic.dart | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/utils/session_logic.dart b/lib/utils/session_logic.dart index dc30127..4e64b8c 100644 --- a/lib/utils/session_logic.dart +++ b/lib/utils/session_logic.dart @@ -1,16 +1,29 @@ -// Session logic for InnerPod. +/// Session logic for InnerPod. // // Time-stamp: <2026-02-10 16:40:00 Amogh Hosamane> // -// Copyright (C) 2024, Togaware Pty Ltd +/// Copyright (C) 2024, Togaware Pty Ltd +/// +/// Licensed under the GNU General Public License, Version 3 (the "License"); +/// +/// License: https://opensource.org/license/gpl-3-0 // -// Licensed under the GNU General Public License, Version 3 (the "License"); +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. // -// License: https://opensource.org/license/gpl-3-0 +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. // -// Authors: Amogh Hosamane +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +/// +/// Authors: Amogh Hosamane -library session_logic; +library; const String _prefixes = ''' @prefix : <#>.