Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ web/**
# Placeholder URLs in documentation

https://github.com/YOUR_USERNAME/innerpod.git
https://github.com/gjwgit/innerpod/blob/dev/README.md


# Licenses. Note that gnu.org is throttled so do not check it.

Expand Down
79 changes: 13 additions & 66 deletions lib/home.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/// A session timer with session logged to your Solid Pod.
// A session timer with session logged to your Solid Pod.
//
// Time-stamp: <Tuesday 2026-02-10 15:44:48 +1100 Graham Williams>
//
/// Copyright (C) 2024-2025, 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-2025, 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
Expand All @@ -22,19 +22,15 @@
// this program. If not, see <https://opensource.org/license/gpl-3-0>.
///
/// Authors: Graham Williams

library;

import 'package:flutter/material.dart';

import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:gap/gap.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:solidpod/solidpod.dart';
import 'package:url_launcher/url_launcher.dart';

import 'package:innerpod/constants/colours.dart';
import 'package:innerpod/utils/word_wrap.dart';
import 'package:innerpod/widgets/about.dart';
import 'package:innerpod/widgets/history.dart';
import 'package:innerpod/widgets/instructions.dart';
import 'package:innerpod/widgets/timer.dart';
Expand All @@ -60,31 +56,15 @@ class HomeState extends State<Home> with SingleTickerProviderStateMixin {
final String _changelogUrl =
'https://github.com/gjwgit/innerpod/blob/dev/CHANGELOG.md';

final _about = wordWrap('''

Inner Pod is an app for timing meditation sessions and, optionally, storing a
log of your meditation sessions to your Pod. A session, in fact, can be
anything. The app is commonly used for contemplative or silent meditation as
is the tradition in many cultures and religions. The concept for the app and
images were generated by large language models.

The app is written in Flutter and the open source code is available from
**github**. You can also run the app **online** through your browser.

**GitHub** [https://github.com/gjwgit/innerpod](https://github.com/gjwgit/innerpod)

**OnLine** [https://innerpod.solidcommunity.au](https://innerpod.solidcommunity.au)

**Author** [Graham&nbsp;Williams](https://togaware.com/graham.williams.html)
''');

// Helper function to load the app name and version.

Future<void> _loadAppInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
setState(() {
_appVersion = packageInfo.version; // Set app version from package info
});
if (mounted) {
setState(() {
_appVersion = packageInfo.version; // Set app version from package info
});
}
}

@override
Expand Down Expand Up @@ -165,40 +145,7 @@ class HomeState extends State<Home> with SingleTickerProviderStateMixin {
const SizedBox(width: 50),
IconButton(
icon: const Icon(Icons.info),
onPressed: () async {
final appInfo = await getAppNameVersion();
final appName = appInfo.name;

// Note the use of the conditional with `context.mounted` to avoid
// the "Don't use 'BuildContext's across async gaps" warning.

if (context.mounted) {
showAboutDialog(
context: context,
applicationIcon: Image.asset(
'assets/images/inner_icon.png',
width: 100,
height: 100,
),
applicationName:
'${appName[0].toUpperCase()}${appName.substring(1)}',
applicationVersion: appInfo.version,
applicationLegalese: '© 2024 Togaware',
children: [
const Gap(20),
MarkdownBody(
data: _about,
selectable: true,
softLineBreak: true,
onTapLink: (text, href, about) {
final url = Uri.parse(href ?? '');
launchUrl(url);
},
),
],
);
}
},
onPressed: () => showAppAboutDialog(context),
tooltip: 'Popup a window about the app.',
),
],
Expand Down
85 changes: 85 additions & 0 deletions lib/widgets/about.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// About dialog for the app.
//
// Time-stamp: <2026-02-12 20:40:00 Graham Williams>
//
// Copyright (C) 2024-2026, 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 <https://opensource.org/license/gpl-3-0>.
///
/// Authors: Graham Williams
library;

import 'package:flutter/material.dart';

import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:gap/gap.dart';
import 'package:solidpod/solidpod.dart';
import 'package:url_launcher/url_launcher.dart';

import 'package:innerpod/utils/word_wrap.dart';

/// Display the about dialog for the app.

Future<void> showAppAboutDialog(BuildContext context) async {
final appInfo = await getAppNameVersion();
final appName = appInfo.name;

final about = wordWrap('''

Inner Pod is an app for timing meditation sessions and, optionally, storing a
log of your meditation sessions to your Pod. A session, in fact, can be
anything. The app is commonly used for contemplative or silent meditation as
is the tradition in many cultures and religions. The concept for the app and
images were generated by large language models.

The app is written in Flutter and the open source code is available from
**github**. You can also run the app **online** through your browser.

**GitHub** [https://github.com/gjwgit/innerpod](https://github.com/gjwgit/innerpod)

**OnLine** [https://innerpod.solidcommunity.au](https://innerpod.solidcommunity.au)

**Author** [Graham&nbsp;Williams](https://togaware.com/graham.williams.html)
''');

if (context.mounted) {
showAboutDialog(
context: context,
applicationIcon: Image.asset(
'assets/images/inner_icon.png',
width: 100,
height: 100,
),
applicationName: '${appName[0].toUpperCase()}${appName.substring(1)}',
applicationVersion: appInfo.version,
applicationLegalese: '© 2024 Togaware',
children: [
const Gap(20),
MarkdownBody(
data: about,
selectable: true,
softLineBreak: true,
onTapLink: (text, href, about) {
final url = Uri.parse(href ?? '');
launchUrl(url);
},
),
],
);
}
}
45 changes: 27 additions & 18 deletions lib/widgets/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ class _HistoryState extends State<History> {
}

Future<void> _loadSessions() async {
setState(() {
_isLoading = true;
});
if (mounted) {
setState(() {
_isLoading = true;
});
}

try {
String? content;
Expand All @@ -49,24 +51,31 @@ class _HistoryState extends State<History> {
// parseSessions handles null content and returns empty list
List<dynamic> 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();
});
if (mounted) {
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('Unexpected error loading sessions: $e');
// If file doesn't exist yet, treat as empty (no sessions)
debugPrint(
'sessions.ttl does not exist yet (this is normal for new users)',
);
} finally {
setState(() {
_isLoading = false;
});
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}

Expand Down
Loading