diff --git a/example/lib/home.dart b/example/lib/home.dart index 59a8ae10..8d2ec9e6 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -59,6 +59,7 @@ import 'package:demopod/features/multiple_resource_sharing.dart'; import 'package:demopod/features/permission_callback_demo.dart'; import 'package:demopod/features/read_acl_inherited_file.dart'; import 'package:demopod/features/view_keys.dart'; +import 'package:demopod/utils/ensure_resource.dart'; import 'package:demopod/utils/rdf.dart'; /// A widget for the demonstration screen of the application. @@ -658,12 +659,27 @@ class HomeState extends State with SingleTickerProviderStateMixin { final loggedIn = await loginIfRequired(context); if (loggedIn) { await getKeyFromUserIfRequired(context, widget); + + // Ensure the target resource exists on the Pod before + // opening the grant permission UI. The button + // previously failed with a "not found" error when + // keyvalue/key-value.ttl had never been created. + + if (!context.mounted) return; + final ready = await ensurePodResourceExists( + context, + relativePath: dataFile, + defaultContent: createDemoTtlStr('key-value'), + ); + if (!ready) return; + + if (!context.mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => const GrantPermissionUi( backgroundColor: titleBackgroundColor, - resourceNames: ['keyvalue/key-value.ttl'], + resourceNames: [dataFile], child: Home(), ), ), @@ -741,6 +757,21 @@ class HomeState extends State with SingleTickerProviderStateMixin { final loggedIn = await loginIfRequired(context); if (loggedIn) { await getKeyFromUserIfRequired(context, widget); + + // Ensure the target resource exists on the Pod before + // opening the shared resources UI. The button + // previously failed with a "not found" error when + // keyvalue/key-value.ttl had never been created. + + if (!context.mounted) return; + final ready = await ensurePodResourceExists( + context, + relativePath: dataFile, + defaultContent: createDemoTtlStr('key-value'), + ); + if (!ready) return; + + if (!context.mounted) return; Navigator.push( context, MaterialPageRoute( diff --git a/example/lib/utils/ensure_resource.dart b/example/lib/utils/ensure_resource.dart new file mode 100644 index 00000000..f0f05aa5 --- /dev/null +++ b/example/lib/utils/ensure_resource.dart @@ -0,0 +1,107 @@ +/// Utility to ensure a Pod resource exists before performing actions on it. +/// +/// Copyright (C) 2026, Software Innovation Institute, ANU. +/// +/// 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: Tony Chen + +// ignore_for_file: use_build_context_synchronously + +library; + +import 'package:flutter/material.dart'; + +import 'package:solidpod/solidpod.dart' + show + ResourceStatus, + checkResourceStatus, + filenameToResourceUrl, + writePod; + +import 'package:demopod/dialogs/alert.dart'; + +/// Ensures the resource at [relativePath] exists in the user's Pod. +/// +/// The [relativePath] is interpreted relative to the app's data directory +/// (e.g. `keyvalue/key-value.ttl`). When the resource is missing on the Pod, +/// a new file is created using [defaultContent] (encrypted by default) so +/// downstream actions such as granting permissions do not fail. +/// +/// Returns `true` when the resource is available (already existed or was +/// just created), and `false` otherwise. + +Future ensurePodResourceExists( + BuildContext context, { + required String relativePath, + required String defaultContent, + bool encrypted = true, +}) async { + try { + final fileUrl = await filenameToResourceUrl(fileName: relativePath); + + final status = await checkResourceStatus(fileUrl); + + switch (status) { + case ResourceStatus.exist: + return true; + + case ResourceStatus.notExist: + await writePod(relativePath, defaultContent, encrypted: encrypted); + + if (context.mounted) { + await alert( + context, + 'The resource "$relativePath" did not exist on your Pod, ' + 'so a new file with placeholder content has been created ' + 'automatically.', + ); + } + return true; + + case ResourceStatus.forbidden: + if (context.mounted) { + await alert( + context, + 'Access to "$relativePath" is forbidden. Please check the ' + 'permissions on your Pod and try again.', + ); + } + return false; + + case ResourceStatus.unknown: + if (context.mounted) { + await alert( + context, + 'Unable to determine whether "$relativePath" exists on your Pod. ' + 'Please try again in a moment.', + ); + } + return false; + } + } on Object catch (e) { + debugPrint('ensurePodResourceExists() failed: $e'); + if (context.mounted) { + await alert( + context, + 'Failed to ensure "$relativePath" exists on your Pod: $e', + ); + } + return false; + } +}