Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions lib/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -108,14 +109,7 @@ class HomeState extends State<Home> with SingleTickerProviderStateMixin {
final List<Widget> _pages = <Widget>[
const Timer(),
const Instructions(),
const Column(
children: [
Gap(100),
Text('Coming Soon'),
Gap(100),
Icon(Icons.chat, size: 150),
],
),
const History(),
];

@override
Expand Down Expand Up @@ -229,7 +223,7 @@ class HomeState extends State<Home> with SingleTickerProviderStateMixin {
label: 'Text',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
icon: Icon(Icons.history),
label: 'History',
),
],
Expand Down
40 changes: 16 additions & 24 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
library;

import 'package:flutter/material.dart';
import 'package:solidpod/solidpod.dart';

import 'package:innerpod/home.dart';

Expand Down Expand Up @@ -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(),
);
}
}
103 changes: 103 additions & 0 deletions lib/widgets/history.dart
Original file line number Diff line number Diff line change
@@ -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<History> createState() => _HistoryState();
}

class _HistoryState extends State<History> {
List<Map<String, String>> _sessions = [];
bool _isLoading = true;

@override
void initState() {
super.initState();
_loadSessions();
}

Future<void> _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<dynamic> 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(),
),
),
),
),
);
}
}
35 changes: 35 additions & 0 deletions lib/widgets/timer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -76,6 +78,10 @@ class TimerState extends State<Timer> {

var _audioDuration = Duration.zero;

// Track the start time of a session.

DateTime? _startTime;

////////////////////////////////////////////////////////////////////////
// CONSTANTS
////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -125,6 +131,7 @@ class TimerState extends State<Timer> {
_reset();
_stopSleep();
_isGuided = false;
_startTime = DateTime.now();

// Good to wait a second before starting the audio after tapping the button,
// otherwise it feels rushed.
Expand Down Expand Up @@ -167,6 +174,7 @@ class TimerState extends State<Timer> {
_reset();
_stopSleep();
_isGuided = true;
_startTime = DateTime.now();

// Good to wait a second before starting the audio after tapping the button,
// otherwise it feels rushed.
Expand Down Expand Up @@ -215,6 +223,32 @@ class TimerState extends State<Timer> {

_reset();
_allowSleep();
await _saveSession();
}

Future<void> _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<dynamic> 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;
}

////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 4 additions & 11 deletions windows/runner/flutter_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down