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);