diff --git a/README.md b/README.md index e90b7cfe..2d02200f 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,15 @@ utilising the solidpod package. ## Introduction -[SolidPod](https://pub.dev/packages/solidpod) provides functionality -for dart applications to manage personal online data stores (Pods) -hosted in a Data Vault on a [Solid -Server](https://solidproject.org). The package provides underlying -functionality relied upon by the -[solidui](https://pub.dev/packages/solidui) package for quickly -building Flutter-based applications. It supports high level access -for an application to authenticate users to their Pods, access the -users' data from their Pods, and then share the data stored in users' -Pods with other Pods through Flutter Widgets. +[SolidPod](https://pub.dev/packages/solidpod) provides the core +business logic for Dart applications to manage personal online data +stores (PODs) hosted in a Data Vault on a [Solid +Server](https://solidproject.org). It supports authenticating users to +their PODs, reading and writing data, and managing access permissions +programmatically. The companion +[solidui](https://pub.dev/packages/solidui) package builds on top of +SolidPod to provide ready-made Flutter widgets for login screens, +permission management, and other user-facing features. ## What is Solid? @@ -55,15 +54,15 @@ visit ## Features -- [Authenticate](#authenticate-example) a user against a given Solid server -and [login](#login-example). -- [Manage security key](#change-security-key-example) for data encryption. +- [Authenticate](#authenticate-example) a user against a given Solid server. - [Read](#read-pod-file-example) and [write](#write-to-pod-file-example) data files -in POD. -- [View](#view-permission-ui-example) and [manage](#grant-permission-ui-example) -file access permissions. +in a POD. - [Read, write and delete](#large-file-manager-example) large data files. +For UI components such as login screens, security key management, +permission granting/revoking, and shared resource views, see the +[solidui](https://pub.dev/packages/solidui) package. + [Solid](https://solidproject.org/) is an open standard for a server providing Data Vaults hosting personal online data stores (Pods). Numerous providers of Solid Server @@ -198,39 +197,6 @@ final authData = await solidAuthenticate( ); ``` -### Login Example - -A simple login screen to authenticate a user against a Solid server. -If your own home widget is called `MyHome()` then simply wrap this within -the `SolidLogin()` widget: - -```dart - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'My Pod', - home: const SolidLogin( - child: Scaffold(body: MyHome()), - ), - ); - } -``` - -### Change Security Key Example - -Wrap the `changeKeyPopup()` function within a button widget. Parameters -include the `BuildContext` and the widget that you need to return to -after changing the key. - -```dart -ElevatedButton( - onPressed: () { - changeKeyPopup(context, ReturnPage()); - }, - child: const Text('Change Security Key on Pod') -) -``` - ### Read Pod File Example Read data from the file `data/myfiles/my-data-file.ttl`. @@ -292,124 +258,6 @@ The above will create a single `.acl` file for the directory `child-2.ttl` files. Also it will create a single key associated with the directory `parentDir` and encrypt both files using that key. -### Grant Permission UI Example - -Wrap the `GrantPermissionUi` widget around a button to navigate to -the grant permission page. - -```dart -ElevatedButton( - child: const Text( - 'Add/Delete Permissions'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GrantPermissionUi( - child: ReturnPage(), - ), - ), - ), -) -``` - -To add/delete permissions of a recipient to a specific user owned file use: - -```dart -ElevatedButton( - child: const Text( - 'Add/Delete Permissions from a Specific File'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GrantPermissionUi( - resourceName: 'my-data-file.ttl', - child: ReturnPage(), - ), - ), - ), -) -``` - -To add/delete permissions of a recipient to a specific user owned directory use: - -```dart -ElevatedButton( - child: const Text( - 'Add/Delete Permissions from a Specific Directory'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GrantPermissionUi( - resourceName: 'parentDir/', - child: ReturnPage(), - isFile: false, - ), - ), - ), -) -``` - -To add/delete permissions of a recipient to a specific externally owned -file (that user has control access to) use: - -```dart -ElevatedButton( - child: const Text( - 'Add/Delete Permissions from a Specific File'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GrantPermissionUi( - resourceName: 'my-data-file.ttl', - isExternalRes: true, - ownerWebId: ownerWebId, - granterWebId: granterWebId, - child: ReturnPage(), - ), - ), - ), -) -``` - -### View Permission UI Example - -Wrap the `SharedResourcesUi` widget around a button to navigate to -the view permission page. - -```dart -ElevatedButton( - child: const Text( - 'View Resources your WebID have access to'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SharedResourcesUi( - child: ReturnPage(), - ), - ), - ), -) -``` - -To view permissions to a specific resource from a specific webID use: - -```dart -ElevatedButton( - child: const Text( - 'View access to specific Resource'), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SharedResourcesUi( - fileName: 'my-data-file.ttl', - sourceWebId: 'https://pods.solidcommunity.au/john-doe/profile/card#me', - child: ReturnPage(), - ), - ), - ), -) -``` - ### Large File Manager Example To upload a large file in application `myapp`, use: diff --git a/example/lib/features/permission_callback_demo.dart b/example/lib/features/permission_callback_demo.dart index 740cc8df..5c48d4e1 100644 --- a/example/lib/features/permission_callback_demo.dart +++ b/example/lib/features/permission_callback_demo.dart @@ -26,6 +26,7 @@ library; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart'; +import 'package:solidui/solidui.dart' show GrantPermissionUi; /// A widget demonstrating the onPermissionGranted callback functionality. diff --git a/example/lib/home.dart b/example/lib/home.dart index 7c9fe9eb..aec5ed3f 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -37,13 +37,15 @@ import 'package:markdown_tooltip/markdown_tooltip.dart'; import 'package:solidpod/solidpod.dart'; import 'package:solidui/solidui.dart' show + GrantPermissionUi, InitialSetupScreenBody, + SharedResourcesUi, + changeKeyPopup, + getKeyFromUserIfRequired, + largeGapV, loginIfRequired, logoutPopup, - getKeyFromUserIfRequired, - changeKeyPopup, - smallGapV, - largeGapV; + smallGapV; import 'package:demopod/constants/app.dart'; import 'package:demopod/dialogs/about.dart'; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 23adda3e..a45d1496 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,7 +25,7 @@ dependency_overrides: solidui: git: url: https://github.com/anusii/solidui - ref: dev + ref: tony/59_ui_migration dev_dependencies: flutter_lints: ^6.0.0 diff --git a/lib/solidpod.dart b/lib/solidpod.dart index f759e189..226dc137 100644 --- a/lib/solidpod.dart +++ b/lib/solidpod.dart @@ -37,7 +37,8 @@ export 'src/solid/constants/solid_constants.dart'; // Legacy exports for backward compatibility (deprecated, use SolidConstants instead) -export 'src/solid/constants/common.dart' show foaf, terms, ResourceStatus; +export 'src/solid/constants/common.dart' + show foaf, terms, ResourceStatus, permStr, agentStr, whatIsWebID, demoWebID; export 'src/solid/constants/schema.dart' show appsTerms; export 'src/solid/constants/path_type.dart' show PathType; @@ -50,14 +51,6 @@ export 'src/solid/constants/predicates.dart'; export 'src/solid/authenticate.dart' show solidAuthenticate; -/// UI class to grant permission for a resource - -export 'src/solid/grant_permission_ui.dart'; - -/// UI class to read permission given to the user webID by others - -export 'src/solid/shared_resources_ui.dart'; - /// Status class to represent different function outputs export 'src/solid/solid_func_call_status.dart' show SolidFunctionCallStatus; @@ -118,6 +111,7 @@ export 'src/solid/utils/misc.dart' getTokensForResource, getDateTime, getEncKeyPath, + isDir, logoutPod, registerLogoutCacheCallback, setAppDirName, @@ -165,8 +159,15 @@ export 'src/solid/read_permission.dart'; export 'src/solid/revoke_permission.dart'; -/// Permission recipient type -export 'src/solid/constants/web_acl.dart' show RecipientType; +/// Permission types and access control utilities +export 'src/solid/constants/web_acl.dart' + show + RecipientType, + AccessMode, + getRecipientType, + getAccessMode, + publicAgent, + authenticatedAgent; /// Functions to upload, download, and delete large file from a Solid server @@ -223,8 +224,36 @@ export 'src/solid/constants/common.dart' show dataDir, profCard, authUserPred; export 'src/solid/get_resources.dart'; -/// 20250917 gjw Extras that were required for the example app! Not yet -/// documented. +/// Check if a resource exists and has an associated ACL file + +export 'src/solid/chk_exists_and_has_acl.dart' show chkExistsAndHasAcl; + +/// Retrieve the list of recipients that have access to files in a user's POD + +export 'src/solid/get_recipient_list.dart' + show getRecipientList, extractRecipWebIdList; + +/// Read permission history of a resource + +export 'src/solid/shared_resource_history.dart' show sharedResourcesHistory; + +/// Standardise retrieval of authoriser (owner or granter) for a resource + +export 'src/solid/utils/get_authoriser.dart' show getAuthoriser; + +/// Data model for permission details + +export 'src/solid/models/permission_details.dart' show PermissionDetails; + +/// Utilities for parsing permission data into the Permission model + +export 'src/solid/utils/permission_helper.dart' + show PermissionHelper, permMapToList; + +/// Data model for log records (permission history entries) + +export 'src/solid/models/log_record.dart' show LogRecord; + +/// Data model for parsed permission entries -export 'package:solidui/solidui.dart' - show SecurityKeyUI, SecurityStrings, changeKeyPopup; +export 'src/solid/models/permission.dart' show Permission; diff --git a/lib/src/solid/common_func.dart b/lib/src/solid/common_func.dart index f9ecd188..3511954f 100644 --- a/lib/src/solid/common_func.dart +++ b/lib/src/solid/common_func.dart @@ -33,7 +33,6 @@ import 'package:flutter/material.dart'; import 'package:solidpod/src/solid/api/rest_api.dart'; import 'package:solidpod/src/solid/constants/common.dart'; import 'package:solidpod/src/solid/constants/schema.dart' show appsTerms; -import 'package:solidpod/src/solid/utils/alert.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; import 'package:solidpod/src/solid/utils/get_url_helper.dart'; import 'package:solidpod/src/solid/utils/init_helper.dart'; @@ -130,6 +129,28 @@ Future deleteDataFileDialog( if (context.mounted) await alert(context, msg); } +/// Show a simple alert dialog with a message and an optional title. + +Future alert( + BuildContext context, + String msg, [ + String title = 'Notice', +]) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(msg), + actions: [ + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); +} + /// Get inherited resource parent directory url String getParentDir(String fileContent, String fileUrl) { final dataMap = parseTTLMap(fileContent); diff --git a/lib/src/solid/grant_permission_form.dart b/lib/src/solid/grant_permission_form.dart deleted file mode 100644 index a3e2ea4e..00000000 --- a/lib/src/solid/grant_permission_form.dart +++ /dev/null @@ -1,427 +0,0 @@ -/// A button for sharing a resource. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' - show ActionColors, GrantPermFormLayout, smallGapV, makeSubHeading; - -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission.dart'; -import 'package:solidpod/src/solid/grant_permission_helper.dart'; -import 'package:solidpod/src/solid/select_recipients.dart'; -import 'package:solidpod/src/solid/show_selected_recipients.dart'; -import 'package:solidpod/src/solid/solid_func_call_status.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; -import 'package:solidpod/src/solid/utils/is_phone.dart'; -import 'package:solidpod/src/solid/utils/snack_bar.dart'; -import 'package:solidpod/src/widgets/group_webid_input.dart'; -import 'package:solidpod/src/widgets/ind_webid_input_screen.dart'; - -/// Sharing (grant permission) form dialog function -/// -/// A [StatefulWidget] for creating a grant permission form -/// dialog to get recipient and access modes to grant for the -/// provided [resourceName] -/// -/// Parameters: -/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource. -/// - [isExternalRes] - Boolean flag describing whether the resource -/// is externally owned. -/// - [ownerWebId] - WebId of the owner of the resource. Required if the resource is externally owned. -/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externally owned. -/// - [accessModeList] - List of access mode options to show. -/// - [recipientTypeList] - List of recipient type options to show. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// - [updatePermissionGrantedFunction] - is the function to be called -/// when permissions are granted successfully -/// - [onPermissionGranted] - Callback function called when permissions are granted successfully. - -class GrantPermissionForm extends StatefulWidget { - /// String to assign the webId of the resource owner. - - final String ownerWebId; - - /// String to assign the external webId of the resource granter. - - final String granterWebId; - - /// The name of the file or directory that access is being granted for. - - final String resourceName; - - final bool isExternalRes; - - /// A flag to determine whether the given resource is a file or not. - - final bool isFile; - - /// The list of access modes to show in form. By default - /// all four types of access mode are listed. - - final List accessModeList; - - /// The list of types of recipients to show in form. By default - /// all four types of recipient are listed. - - final List recipientTypeList; - - /// Map of data files on a user's POD used to extract the - /// user's recipient list by the WebIdTextInputScreen. - /// If not provided, the WebIdTextInputScreen will read the - /// user's files in their app data folder on their Pod to - /// fetch the ACLs needed to derive the user's recipient list. - - final Map dataFilesMap; - - /// Function run to update permissions table - - final Function updatePermissionsFunction; - - /// Function when permissions are granted successfully - - final Function updatePermissionGrantedFunction; - - /// Callback function called when permissions are granted successfully. - - final VoidCallback? onPermissionGranted; - - const GrantPermissionForm({ - super.key, - required this.updatePermissionsFunction, - required this.resourceName, - required this.ownerWebId, - required this.granterWebId, - this.accessModeList = const ['read', 'write', 'append', 'control'], - this.recipientTypeList = const ['public', 'indi', 'auth', 'group'], - required this.isExternalRes, - required this.isFile, - required this.updatePermissionGrantedFunction, - this.dataFilesMap = const {}, - this.onPermissionGranted, - }); - - @override - State createState() => _GrantPermissionFormState(); -} - -class _GrantPermissionFormState extends State { - /// Selected recipient - - RecipientType selectedRecipientType = RecipientType.none; - - /// Selected recipient details - - String selectedRecipientDetails = ''; - - /// List of webIds for group permission - - List finalWebIdList = []; - - /// Selected group name - - String selectedGroupName = ''; - - /// Selected list of permissions - - List selectedPermList = []; - - /// Flag to track if permissions were granted successfully. - - bool permissionsGrantedSuccessfully = false; - - /// read permission checked flag - - bool readChecked = false; - - /// write permission checked flag - - bool writeChecked = false; - - /// control permission checked flag - - bool controlChecked = false; - - /// append permission checked flag - - bool appendChecked = false; - - /// Public permission check flag - - bool publicChecked = false; - - /// Define access mode list - - List accessModeList = []; - - @override - void initState() { - super.initState(); - - // Load access mode list to be displayed - for (final accessModeStr in widget.accessModeList) { - accessModeList.add(getAccessMode(accessModeStr)); - } - } - - @override - void dispose() { - super.dispose(); - } - - /// Private function to call alert dialog in share resource button - /// context. This provides an alert dialog over the top of the - /// grant permission form dialog. - Future _alert(String msg) async => alert(context, msg); - - /// Private function to show snackbar in share resource button context - Future _showSnackBar( - String msg, - Color bgColor, { - Duration duration = const Duration(seconds: 4), - }) async => - showSnackBar(context, msg, bgColor, duration: duration); - - /// Update selected webid list with individual recipient webid - /// [receiverWebId]. - void updateIndWebIdInput(String receiverWebId) => setState(() { - selectedRecipientDetails = receiverWebId; - finalWebIdList = [receiverWebId]; - }); - - /// Update selected webid list with list of webids in - /// recipient group [webIdList] and their group name - /// [groupName]. - - void updateGroupWebIdInput(String groupName, List webIdList) => - setState(() { - selectedRecipientDetails = webIdList.join(', '); - finalWebIdList = webIdList; - selectedGroupName = groupName; - }); - - /// Update checked status of access mode boxes to show - /// selected access modes. - void updateCheckbox(bool newValue, AccessMode accessMode) => setState(() { - switch (accessMode) { - case AccessMode.read: - readChecked = newValue; - case AccessMode.write: - writeChecked = newValue; - case AccessMode.control: - controlChecked = newValue; - case AccessMode.append: - appendChecked = newValue; - } - if (newValue) { - selectedPermList.add(accessMode.mode); - } else { - selectedPermList.remove(accessMode.mode); - } - }); - - /// Define button click actions for each recipient type button - - /// Set recipients to public - void _setRecipientsToPublic() => setState(() { - selectedRecipientType = RecipientType.public; - selectedRecipientDetails = 'Anyone (release publicly)'; - finalWebIdList = [publicAgent.value]; - }); - - /// Set recipients to authorised users - void _setRecipientsToAuthUsers() => setState(() { - selectedRecipientType = RecipientType.authUser; - selectedRecipientDetails = - 'Authenticated Users (any user logged in with their webId)'; - finalWebIdList = [authenticatedAgent.value]; - }); - - /// Select individual recipient - void _setRecipientsToIndividual() => setState(() { - selectedRecipientType = RecipientType.individual; - }); - - /// Select a group of recipients - void _setRecipientsToGroup() => setState(() { - selectedRecipientType = RecipientType.group; - }); - - @override - Widget build(BuildContext context) { - return AlertDialog( - insetPadding: GrantPermFormLayout.contentPadding, - title: Text('Share ${widget.resourceName}'), - content: Scrollbar( - thumbVisibility: true, - child: SingleChildScrollView( - primary: true, - child: SizedBox( - // Use full width on phones, else use a preset narrower width - width: (!isPhone()) - ? GrantPermFormLayout.dialogWidth - : double.maxFinite, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - makeSubHeading('Select the recipient/s of file access'), - - // Show Select Recipient Buttons - SelectRecipients( - isExternalRes: widget.isExternalRes, - recipientTypeList: widget.recipientTypeList, - setPublicFunction: _setRecipientsToPublic, - setAuthUsersFunction: _setRecipientsToAuthUsers, - setIndividualFunction: _setRecipientsToIndividual, - setGroupFunction: _setRecipientsToGroup, - ), - - // Select Individual recipient if required - if (selectedRecipientType == RecipientType.individual) ...[ - IndWebIdInputScreen( - onSubmitFunction: updateIndWebIdInput, - dataFilesMap: widget.dataFilesMap, - ), - ] else if (selectedRecipientType == RecipientType.group) ...[ - // Select group of recipients if required - GroupWebIdTextInput(onSubmitFunction: updateGroupWebIdInput), - ], - // List selected recipient webids or recipient - // type (public/auth) - ShowSelectedRecipients( - selectedRecipientType: selectedRecipientType, - selectedRecipientDetails: selectedRecipientDetails, - selectedGroupName: selectedGroupName, - ), - smallGapV, - makeSubHeading('Select one or more file access permissions'), - // Show access mode checkboxes and update - // selection status on click - ...getPermissionCheckBoxes( - accessModeList, - modeSwitches: { - AccessMode.read: readChecked, - AccessMode.write: writeChecked, - AccessMode.control: controlChecked, - AccessMode.append: appendChecked, - }, - onUpdate: updateCheckbox, - ), - ], - ), - ), - ), - ), - actions: [ - TextButton( - onPressed: () async { - // Grant Permission and update permission map - // used by permission table - - if (selectedRecipientType.type.isNotEmpty) { - if (selectedPermList.isNotEmpty) { - SolidFunctionCallStatus result; - try { - // Update ACL and permission logs to grant permission - result = await grantPermission( - fileName: widget.resourceName, - isFile: widget.isFile, - permissionList: selectedPermList, - recipientType: selectedRecipientType, - recipientWebIdList: finalWebIdList, - ownerWebId: widget.ownerWebId, - granterWebId: widget.granterWebId, - isExternalRes: widget.isExternalRes, - groupName: selectedGroupName, - ); - - // Close grant permission dialog - if (!context.mounted) return; - Navigator.of(context).pop(); - } on Object catch (e, stackTrace) { - result = SolidFunctionCallStatus.fail; - debugPrintException(e, stackTrace); - } - - if (result == SolidFunctionCallStatus.success) { - _showSnackBar(successMsg, ActionColors.success); - // Update permissions table - await widget.updatePermissionsFunction( - widget.resourceName, //_resourceName, - isFile: widget.isFile, - isExternalRes: widget.isExternalRes, - ); - - // Mark permissions as granted successfully for callback tracking - await widget.updatePermissionGrantedFunction(); - - // Trigger the onPermissionGranted callback if provided - widget.onPermissionGranted?.call(); - } else if (result == SolidFunctionCallStatus.fail) { - // More detailed error message with troubleshooting tips - _showSnackBar(failureMsg, ActionColors.error); - - // Also log to console for debugging - debugPrintFailure( - widget.resourceName, // _resourceName, - finalWebIdList, - selectedPermList, - ); - } else if (result == SolidFunctionCallStatus.notInitialised) { - _showSnackBar(podNotInitMsg, ActionColors.warning); - } else { - await _alert(updatePermissionMsg); - } - } else { - await _alert( - 'Please select one or more file access permissions', - ); - } - } else { - await _alert('Please select a type of recipient'); - } - }, - child: const Text('Grant Permission'), - ), - TextButton( - onPressed: () { - // Close dialog - Navigator.of(context).pop(); - }, - child: const Text('Cancel'), - ), - ], - ); - } -} diff --git a/lib/src/solid/grant_permission_helper.dart b/lib/src/solid/grant_permission_helper.dart deleted file mode 100644 index 4c0aa415..00000000 --- a/lib/src/solid/grant_permission_helper.dart +++ /dev/null @@ -1,173 +0,0 @@ -/// A screen to demonstrate the data sharing capabilities of PODs. -/// -// Time-stamp: -/// -/// Copyright (C) 2024, Software Innovation Institute, ANU. -/// -/// Licensed under the GNU General Public License, Version 3 (the "License"). -/// -/// License: https://www.gnu.org/licenses/gpl-3.0.en.html. -// -// 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: Anushka Vidanage, Jess Moore, Ashley Tang, Dawei Chen - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' show SharingPageLayout; - -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/widgets/permission_checkbox.dart'; - -const recipientToolTips = { - RecipientType.public: ''' - **Public:** This file will be publicly - accessible so that even users without a - Data Vault can access the file. - ''', - RecipientType.authUser: ''' -**Users:** The file will be available to -any user who has registered a Data -Vault. When they have logged into their -Data Vault they will be able to access -the file. -''', - RecipientType.individual: ''' -**Individual:** The file will be available -only to the identified individual user. A -WebID is required to identify the -individual who is gratned access to the -file. -''', - RecipientType.group: ''' -**Group:** A collection of WebIDs can be -provided so that as a group they can -access the file. -''', -}; - -// const selectRecipientPermissionStr = -// 'Select the recipient/s of file access permissions'; -// const selectFilePermissionStr = 'Select the list of file access permissions'; -// const grantPermissionStr = 'Granted file access permissions'; -// const selectPermissionMsg = 'Please select one or more file access permissions'; -// const selectRecipientTypeMsg = 'Please select a type of recipient'; -const updatePermissionMsg = - 'Please login first to update file access permission'; -const podNotInitMsg = - 'The owner of one or more WebIds you entered have not initialised their PODs yet! They need to login and setup their POD first.'; -const noAclMsg = 'Resource does not have a corresponding ACL file.\n' - 'If the ACL is inherited, provide parent directory as the resource name!'; -const successMsg = 'File access permissions granted successfully!'; -const failureMsg = - 'Permission granting failed. Check console logs for details. Common issues: resource not found, invalid WebID format, or network connectivity.'; - -String getFailureMsg(String fileName) => - '❌ [GrantPermissionUI] Permission granting failed for file: $fileName'; - -String getRecipientMsg(List? finalWebIdList) => - '🎯 [GrantPermissionUI] Recipients: $finalWebIdList'; - -String getPermissionMsg(List permissionList) => - '🔐 [GrantPermissionUI] Permissions: $permissionList'; - -String getExceptionMsg(Object e) => - '💥 [GrantPermissionUI] Exception in grantPermission: $e'; - -String getStackTraceMsg(StackTrace stackTrace) => - '📚 [GrantPermissionUI] Stack trace: $stackTrace'; - -void debugPrintException(Object e, StackTrace stackTrace) { - debugPrint(getExceptionMsg(e)); - debugPrint(getStackTraceMsg(stackTrace)); -} - -void debugPrintFailure( - String fileName, - List? finalWebIdList, - List permissionList, -) { - debugPrint(getFailureMsg(fileName)); - debugPrint(getRecipientMsg(finalWebIdList)); - debugPrint(getPermissionMsg(permissionList)); -} - -/// Relevant recipients types for resource sharing by the resource owner. -const ownerRecipientTypes = [ - RecipientType.public, - RecipientType.authUser, - RecipientType.individual, - RecipientType.group, -]; - -/// Relevant recipient types for resource sharing by the resource granter (ie. an entity with control access). -const granterRecipientTypes = [RecipientType.individual, RecipientType.group]; - -/// Get title of sharing page -String makeSharingTitleStr({String? fileName, bool isFile = false}) => - fileName != null - ? isFile - ? 'Share $fileName' - : 'Share $fileName folder' - : 'Share your data with other user\'s PODs'; - -List getPermissionCheckBoxes( - List accessModes, { - required Map modeSwitches, - required Function onUpdate, -}) => - [ - for (final mode in AccessMode.getAllModes()) - if (accessModes.contains(mode)) - permissionCheckbox(mode, modeSwitches[mode]!, onUpdate), - ]; - -Widget getResourceForm({ - required TextEditingController formController, - required bool isFile, - required void Function(bool) onResourceTypeChange, -}) => - Padding( - padding: SharingPageLayout.inputPadding, - child: Column( - children: [ - TextFormField( - controller: formController, - decoration: const InputDecoration( - hintText: - 'Data file path (inside your data folder, Eg: personal/about.ttl)', - ), - validator: (value) => - (value == null || value.isEmpty) ? 'Empty field' : null, - ), - const SizedBox(height: 10), - SwitchListTile( - title: const Text( - 'Is a File?', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - subtitle: Text(isFile ? 'Yes' : 'No'), - value: isFile, - onChanged: onResourceTypeChange, - thumbColor: WidgetStateProperty.resolveWith( - (Set states) => - states.contains(WidgetState.selected) ? Colors.green : null, - ), - ), - ], - ), - ); diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart deleted file mode 100644 index afe410b7..00000000 --- a/lib/src/solid/grant_permission_ui.dart +++ /dev/null @@ -1,666 +0,0 @@ -// A screen to demonstrate the data sharing capabilities of PODs. -/// -// Time-stamp: -/// -/// Copyright (C) 2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Anushka Vidanage, Jess Moore, Ashley Tang, Dawei Chen - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart'; - -import 'package:solidpod/src/solid/chk_exists_and_has_acl.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission_helper.dart'; -import 'package:solidpod/src/solid/models/log_record.dart'; -import 'package:solidpod/src/solid/models/permission_details.dart'; -import 'package:solidpod/src/solid/permission_history.dart'; -import 'package:solidpod/src/solid/permission_table.dart'; -import 'package:solidpod/src/solid/read_permission.dart'; -import 'package:solidpod/src/solid/share_resource_button.dart'; -import 'package:solidpod/src/solid/shared_resource_history.dart'; -import 'package:solidpod/src/solid/solid_func_call_status.dart'; -import 'package:solidpod/src/solid/utils/get_authoriser.dart'; -import 'package:solidpod/src/widgets/app_bar.dart'; - -/// A [StatefulWidget] for showing and editing access permissions to a -/// resource. It displays the permission table of users with access, and -/// allows the user to change access permissions: by granting access -/// to others, changing a recipients access permissions or revoking -/// access permissions. -/// -/// Parameters: -/// - [child] - the child widget to return to. -/// - [title] - Page title to show in the app bar. -/// - [backgroundColor] - Background color. -/// - [showAppBar] - Boolean flag describing whether to show app bar. -/// - [isExternalRes] - Boolean flag describing whether the resource -/// is externally owned. -/// - [accessModeList] - List of access mode options to show. -/// - [recipientTypeList] - List of recipient type options to show. -/// - [ownerWebId] - WebId of the owner of the resource. Required if the resource is externally owned. -/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externall owned. -/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [customAppBar] - Specify a custom app bar widget. -/// - [onPermissionGranted] - Callback function called when permissions are granted successfully. -/// - [onNavigateBack] - Callback function called when navigating back from the screen. - -class GrantPermissionUi extends StatefulWidget { - const GrantPermissionUi({ - required this.child, - this.title = 'Demonstrating data sharing functionality', - this.backgroundColor = const Color.fromARGB(255, 210, 210, 210), - this.showAppBar = true, - this.isExternalRes = false, - this.accessModeList = const ['read', 'write', 'append', 'control'], - this.recipientTypeList = const ['public', 'indi', 'auth', 'group'], - this.ownerWebId, - this.granterWebId, - this.resourceName, - this.isFile = true, - this.dataFilesMap = const {}, - this.customAppBar, - this.onPermissionGranted, - this.onNavigateBack, - super.key, - }) : assert( - // Requires ownerWebId if resource - // is an externally owned. - isExternalRes == false || ownerWebId != null, - 'ownerWebId must be provided if isExternalRes == true', - ); - - /// The child widget to return to when back button is pressed and/or when - /// page is reloaded after a permission is granted or revoked. - - final Widget child; - - /// The text appearing in the app bar. - - final String title; - - /// The text appearing in the app bar. - - final Color backgroundColor; - - /// The boolean to decide whether to display an app bar or not. - - final bool showAppBar; - - /// The boolean to decide whether the resources is from an external POD or not - - final bool isExternalRes; - - /// String to assign the webId of the resource owner. Must - /// be set if [isExternalRes] is set to true. - - final String? ownerWebId; - - /// String to assign the external webId of the resource granter. Must - /// be set if [isExternalRes] is set to true. - - final String? granterWebId; - - /// The list of access modes to be displayed. By default all four types of - /// access mode are listed. - - final List accessModeList; - - /// The list of types of recipients receiving permission to access the resource. By default all four types of recipient are listed. - - final List recipientTypeList; - - /// The name of the file or directory permission is being set to. This is a - /// non required parameter. If not set there will be a text field to define - /// the file name. If [isExternalRes] is set to true this must be set and the - /// value should be the url of the resource. - - final String? resourceName; - - /// A flag to determine whether the given resource is a file or not. This is - /// a parameter with default value true. In the case where [resourceName] is - /// not set there will be a toggle to define this parameter. - /// If [isExternalRes] is set to true this must be set and the value should - /// be the url of the resource. Also if [resourceName] is set this flag must - /// also be set - - final bool isFile; - - /// Map of data files on a user's POD used to extract the - /// user's recipient list by the WebIdTextInputScreen. - /// If not provided, the WebIdTextInputScreen will read the - /// user's files in their app data folder on their Pod to - /// fetch the ACLs needed to derive the user's recipient list. - - final Map dataFilesMap; - - /// App specific app bar - - final PreferredSizeWidget? customAppBar; - - /// Callback function called when permissions are granted successfully. - - final VoidCallback? onPermissionGranted; - - /// Callback function called when navigating back from the screen. - - final VoidCallback? onNavigateBack; - - @override - GrantPermissionUiState createState() => GrantPermissionUiState(); -} - -/// Class to build a UI for granting permission to a given file - -class GrantPermissionUiState extends State - with SingleTickerProviderStateMixin { - /// Flag to check whether permission table is initialised. - - bool permTableInitialied = false; - - /// Flag to check whether permission history is initialised. - - bool permHistoryInitialied = false; - - /// Define access mode list - - List accessModeList = []; - - /// Define recipient type list - - List recipientTypeList = []; - - /// Filename text controller - - final fileNameController = TextEditingController(); - - /// Permission data map of a file - - Map permDataMap = {}; - - /// Owner WebId - - String _ownerWebId = ''; - - /// Granter WebId - - String _granterWebId = ''; - - /// File name of the current permission data map - - String permDataFile = ''; - - /// Flag to track if permissions were granted successfully. - - bool permissionsGrantedSuccessfully = false; - - /// Pod data list retreived as a Future - - late Future getACLPerm; - - /// Permission history list retreived as a Future - - late Future> getPermHistoryList; - - /// Permission history list - - List permHistoryList = []; - - /// Unfiltered permission history list - - List unFilteredPermHistoryList = []; - - /// Flag to check whether permission history is initialised. - - bool showCurrentPermOnly = false; - - /// A flag to identify if the resource is a file or not - bool isFile = true; - - /// Gets permission details data from ACL on POD server if necessary. - - Future loadACLData( - String resName, { - bool isFile = true, - bool isExternalRes = false, - }) async { - final SolidFunctionCallStatus response = await chkExistsAndHasAcl( - fileName: resName, - isFile: isFile, - isExternalRes: isExternalRes, - ); - - switch (response) { - case SolidFunctionCallStatus.aclFound: - - // Permission map from ACL of resource - final Map result = await readPermission( - fileName: resName, - isFile: isFile, - isExternalRes: isExternalRes, - ); - - // Permission Details object to store permission map from ACL, and owner - // and granter of a resource. - - final permissionDetails = PermissionDetails( - permissionMap: result, - ownerWebId: await getAuthoriser( - isExternalRes: isExternalRes, - webId: widget.ownerWebId, - ), - granterWebId: await getAuthoriser( - isExternalRes: isExternalRes, - isGranter: true, - ), - ); - - return permissionDetails; - - case SolidFunctionCallStatus.notLoggedIn: - await _alert('Please login first to retrieve permission'); - - case SolidFunctionCallStatus.noAclFound: - await _alert(noAclMsg); - - default: - await _alert('Unknown error'); - } - - return null; - } - - @override - void initState() { - super.initState(); - // Load permission map from ACL, owner and granter web ids - if (widget.resourceName != null) { - getACLPerm = loadACLData( - widget.resourceName as String, - isFile: widget.isFile, - isExternalRes: widget.isExternalRes, - ); - getPermHistoryList = - sharedResourcesHistory(resourceName: widget.resourceName as String); - // permHistoryList = []; - } - } - - /// Update the permission data map - - Future _updatePermissions( - String fileName, { - bool isFile = true, - bool isExternalRes = false, - }) async { - final pdata = await loadACLData( - fileName, - isFile: isFile, - isExternalRes: isExternalRes, - ); - final updatedPermHistoryList = - await sharedResourcesHistory(resourceName: fileName); - - assert(pdata != null); - - if (pdata!.permissionMap.isEmpty) { - await _alert('We could not find a resource by the name $fileName'); - } else { - setState(() { - permDataMap = pdata.permissionMap; - permDataFile = fileName; - _ownerWebId = pdata.ownerWebId; - _granterWebId = pdata.granterWebId; - }); - } - - if (updatedPermHistoryList.isEmpty) { - await _alert( - 'We could not find permission log entries for resource by the name $fileName', - ); - } else { - setState(() { - permHistoryList = updatedPermHistoryList; - // Set full unfiltered list to current list from updated - // log fetch - unFilteredPermHistoryList = updatedPermHistoryList; - }); - } - } - - // Search log records - void _searchLogs(String enteredKeyword) { - bool found(it) => it.toLowerCase().contains(enteredKeyword.toLowerCase()); - - List results = []; - if (enteredKeyword.isEmpty) { - // Display all log records if no search string - results = unFilteredPermHistoryList; - // permHistoryList; - } else { - // Display log records with recipient name, granter name, - // permission type, permission matches - results = unFilteredPermHistoryList.where((item) { - return [ - item.recipientName, - item.granterName, - item.permissionType, - item.permissionList, - ].map(found).any((result) => result); - }).toList(); - } - - // Refresh the UI - setState(() { - permHistoryList = results; - }); - } - - /// Filter log records for current/all log records - void getLatestLogRecords() { - List currentLogRecords = []; - List currentRecipients = []; - - // Loop through logs and get the latest for each resource - for (final record in permHistoryList) { - // Store most recent grant record - if ((record.permissionType).contains('grant')) { - final recipientWebId = record.recipientWebId; - - currentRecipients = - currentLogRecords.map((item) => item.recipientWebId).toList(); - - if (currentRecipients.contains(recipientWebId)) { - final int prevMatchIndex = currentLogRecords - .indexWhere((item) => item.recipientWebId == recipientWebId); - final String prevDateTime = - currentLogRecords[prevMatchIndex].dateTimeStr; - // Update record if this record more recent than stored record - if ([0, 1].contains( - DateTime.parse(record.dateTimeStr) - .compareTo(DateTime.parse(prevDateTime)), - )) { - currentLogRecords[prevMatchIndex] = record; - } - } else { - // Store record if no prev record for this recipient - currentLogRecords.add(record); - } - } else { - // Skip revoke records - continue; - } - } - - // Refresh the UI - setState(() { - permHistoryList = currentLogRecords; - }); - } - - /// Private function to call alert dialog in grant permission UI context - Future _alert(String msg) async => alert(context, msg); - - /// Build the main widget - Widget _buildPermPage( - BuildContext context, [ - PermissionDetails? initPermDetails, - List? initPermHistoryList, - ]) { - // Check if future is set or not. If set display the permission map - if (initPermDetails != null && permTableInitialied == false) { - permDataMap = initPermDetails.permissionMap; - _ownerWebId = initPermDetails.ownerWebId; - _granterWebId = initPermDetails.granterWebId; - permDataFile = widget.resourceName!; - permTableInitialied = true; - } - - if (initPermHistoryList != null && permHistoryInitialied == false) { - permHistoryList = initPermHistoryList; - // Set full unfiltered list to current list from initial - // log fetch - unFilteredPermHistoryList = initPermHistoryList; - permHistoryInitialied = true; - } - - final retrievePermissionButton = ElevatedButton( - child: const Text('Retrieve permissions'), - onPressed: () async { - final fileName = fileNameController.text; - if (fileName.isEmpty) { - await _alert('Please enter a file name'); - } else { - await _updatePermissions(fileName, isFile: isFile); - } - }, - ); - - bool getIsFile() => widget.resourceName != null ? widget.isFile : isFile; - - // Use customAppBar if provided - final customAppBar = widget.customAppBar ?? - defaultAppBar( - context, - widget.title, - widget.backgroundColor, - widget.child, - onNavigateBack: () => widget.onNavigateBack?.call(), - getResult: () => permissionsGrantedSuccessfully, - ); - - return LayoutBuilder( - builder: (context, constraints) { - return Scaffold( - // Display app bar if showAppBar selected - // AppBar will be defaultAppBar() if customAppBar() - // not provided - appBar: widget.showAppBar ? customAppBar : null, - - body: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - children: [ - smallGapV, - // Sharing heading - makeHeading( - makeSharingTitleStr( - fileName: widget.resourceName, - isFile: widget.isFile, - ), - bold: false, - addColor: false, - addPadding: false, - ), - smallGapV, - // Choose resource and show _updatePermissions button - if (widget.resourceName == null) ...[ - getResourceForm( - formController: fileNameController, - isFile: isFile, - onResourceTypeChange: (bool v) => - setState(() => isFile = v), - ), - smallGapV, - retrievePermissionButton, - smallGapV, - ], - ShareResourceButton( - resourceName: widget.resourceName, - fileNameController: fileNameController, - accessModeList: widget.accessModeList, - recipientTypeList: widget.recipientTypeList, - updatePermissionsFunction: _updatePermissions, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - isExternalRes: widget.isExternalRes, - isFile: widget.isFile, - dataFilesMap: widget.dataFilesMap, - onPermissionGranted: widget.onPermissionGranted, - ), - - mediumGapV, - makeSubHeading( - showCurrentPermOnly - ? 'People with current access' - : 'Permission history', - addPadding: false, - ), - smallGapV, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 5.0, - children: [ - Expanded( - flex: 3, - // Search logs field - child: showCurrentPermOnly - ? const Text('') - : TextField( - onChanged: (value) => _searchLogs(value), - decoration: const InputDecoration( - labelText: - 'Search access level, permission type, recipient or granter name', - labelStyle: TextStyle(fontSize: 12), - hintText: 'Enter search text', - hintStyle: TextStyle(fontSize: 12), - prefixIcon: Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(25.0)), - ), - ), - ), - ), - // Current/History permission switch - SizedBox( - width: 170.0, - child: MarkdownTooltip( - message: - 'Switch between current people with access and permission history log', - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - spacing: 5.0, - children: [ - SizedBox( - width: 100, - child: Text( - showCurrentPermOnly - ? 'Current Permissions' - : 'All Permissions', - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.end, - ), - ), - Switch( - // This bool value toggles the switch. - value: showCurrentPermOnly, - activeThumbColor: ActionColors.success, - onChanged: (bool value) { - // This is called when the user toggles the switch. - setState(() { - showCurrentPermOnly = value; - }); - // If showing all permission - // default to full permission history - // list - if (!showCurrentPermOnly) { - setState(() { - permHistoryList = unFilteredPermHistoryList; - }); - } - }, - ), - ], - ), - ), - ), - ], - ), - - vSmallGapV, - - // Show permissions from ACL if showCurrentPermOnly - // is selected, else show searchable permission - // history. - - showCurrentPermOnly - ? PermissionTable( - resourceName: permDataFile, - permDataMap: permDataMap, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - updatePermissionsFunction: _updatePermissions, - parentWidget: widget.child, - isFile: getIsFile(), - isExternalRes: widget.isExternalRes, - constraints: constraints, - ) - : PermissionHistory( - // Force history rebuild on permission history change. - - key: ValueKey(permHistoryList), - resourceName: widget.resourceName!, - permHistory: permHistoryList, - constraints: constraints, - ), - ], - ), - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context) => widget.resourceName == null - ? _buildPermPage(context) - : FutureBuilder( - future: Future.wait([ - // Future that returns List of current access from ACL - getACLPerm, - // Future that returns List from permission log - getPermHistoryList, - ]), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Scaffold(body: loadingScreen(normalLoadingScreenHeight)); - } - final PermissionDetails initCurrentPerm = - snapshot.data![0] as PermissionDetails; - final List initPermHistoryList = - snapshot.data![1] as List; - return initCurrentPerm.permissionMap.isEmpty - ? widget.child - : _buildPermPage(context, initCurrentPerm, initPermHistoryList); - }, - ); -} diff --git a/lib/src/solid/permission_history.dart b/lib/src/solid/permission_history.dart deleted file mode 100644 index f6858923..00000000 --- a/lib/src/solid/permission_history.dart +++ /dev/null @@ -1,177 +0,0 @@ -/// Table listing permission history of a resource. -/// -// Time-stamp: -/// -/// Copyright (C) 2026, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart' - show WindowSize, ListItemSize, ListIconSize, listIconShape; - -import 'package:solidpod/src/solid/models/log_record.dart'; - -/// A [StatefulWidget] for listing the permission history of a resource. -/// -/// Parameters: -/// - [resourceName] - The filename or file url of the resource. -/// - [permHistory] is the [List] comprising permission history for the [resourceName]. -/// - -class PermissionHistory extends StatefulWidget { - /// The name of the file or directory for which permissions are being - /// shown. - - final String resourceName; - - /// Map of access permission data being displayed for [resourceName]. - - final List permHistory; - - /// Layout constraints - - final BoxConstraints constraints; - - const PermissionHistory({ - super.key, - required this.resourceName, - required this.permHistory, - required this.constraints, - }); - - @override - State createState() => _PermissionHistoryState(); -} - -class _PermissionHistoryState extends State { - /// Searched/sorted logs - List _permHistory = []; - - /// Aspect ratio (width / height) for gridview - /// cards to display log items - late double cardAspectRatio = 2.0; - - /// Boolean describing whether window is narrow - late bool isNarrow; - - /// Scroll controller for single child scroll view - late final ScrollController _scrollController; - - @override - void initState() { - super.initState(); - - // By default _permissions is the full list of permissions - _permHistory = widget.permHistory; - - // Create scroll controller - _scrollController = ScrollController(); - } - - @override - void dispose() { - _scrollController.dispose(); // Dispose the ScrollController - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // Derive whether window is narrow - isNarrow = WindowSize().isNarrowWindow(widget.constraints); - // Calculate the aspect radio for grid cards - cardAspectRatio = - ListItemSize().calculateCardAspectRatio(widget.constraints); - - return Expanded( - child: GridView.builder( - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - // Aspect ratio calculated from LayoutBuilder box constraints - crossAxisCount: 1, - childAspectRatio: cardAspectRatio, - ), - padding: const EdgeInsets.all(10), - itemCount: _permHistory.length, //widget.permDataMap.length, - itemBuilder: (context, index) => Card( - child: Center( - child: Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - child: MarkdownTooltip( - message: _permHistory[index].toolTip, - child: ListTile( - // Leading icon denoting agreement of access terms - leading: SizedBox( - width: ListIconSize.width, - child: Center( - child: Ink( - padding: const EdgeInsets.all(8), - decoration: listIconShape, - child: Icon( - _permHistory[index].permissionType == 'grant' - ? Icons.person_add - : Icons.person_remove, - ), - // 20260201 jesscmoore Alternatives tried: - // insert_drive_file, list, lock and lock_open, mode_edit,my_library_books, note, notes, person_add, person_remove, playlist_add, playlist_remove, public, public_off, post_add, receipt, receipt_long, recent_actors, - ), - ), - ), - // Permission item title - - title: Text( - _permHistory[index].permissionType == 'grant' - ? '${_permHistory[index].dateTime}: ' - '${_permHistory[index].recipientName} ' - '${_permHistory[index].permissionTypeLabel} ' - '${_permHistory[index].permissionList} ' - 'access' - : '${_permHistory[index].dateTime}: ' - '${_permHistory[index].permissionList} ' - 'access ${_permHistory[index].permissionTypeLabel} to ' - '${_permHistory[index].recipientName}', - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - 'Granter: ${_permHistory[index].granterName}', - maxLines: 3, // Limit lines - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/solid/permission_table.dart b/lib/src/solid/permission_table.dart deleted file mode 100644 index 39fcbd76..00000000 --- a/lib/src/solid/permission_table.dart +++ /dev/null @@ -1,223 +0,0 @@ -/// Table listing permissions of a resource. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart' - show WindowSize, ListItemSize, ListIconSize, listIconShape; - -import 'package:solidpod/src/solid/models/permission.dart'; -import 'package:solidpod/src/solid/revoke_permission_button.dart'; -import 'package:solidpod/src/solid/utils/permission_helper.dart'; - -/// A [StatefulWidget] for listing the permissions of a resource. -/// -/// Parameters: -/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource. -/// - [permDataMap] is the map of permission data for the [resourceName] -/// - [ownerWebId] - WebId of the owner of the resource. Required if the resource is externally owned. -/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externally owned. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [isExternalRes] - Boolean flag describing whether the resource -/// is externally owned. -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// - [parentWidget] is the widget to return to after an action Eg: deletion of a -/// permission -/// - -class PermissionTable extends StatefulWidget { - /// The name of the file or directory for which permissions are being - /// shown. - - final String resourceName; - - /// Map of access permission data being displayed for [resourceName]. - - final Map permDataMap; - - /// WebId of the resource owner. - - final String ownerWebId; - - /// WebId of the user granting/revoking access to the resource. - - final String granterWebId; - - /// A flag denoting whether resource is externally owned. - - final bool isExternalRes; - - /// A flag to determine whether the given resource is a file or not. - - final bool isFile; - - /// Function run to update permissions table - - final Function updatePermissionsFunction; - - /// Parent widget to return to. - - final Widget parentWidget; - - /// Layout constraints - - final BoxConstraints constraints; - - const PermissionTable({ - super.key, - required this.resourceName, - required this.permDataMap, - required this.ownerWebId, - required this.granterWebId, - required this.updatePermissionsFunction, - required this.parentWidget, - required this.isFile, - this.isExternalRes = false, - required this.constraints, - }); - - @override - State createState() => _PermissionTableState(); -} - -class _PermissionTableState extends State { - /// Searched/sorted notes - List _permissions = []; - - /// Aspect ratio (width / height) for gridview - /// cards to display note items - late double cardAspectRatio = 2.0; - - /// Boolean describing whether window is narrow - late bool isNarrow; - - /// Scroll controller for single child scroll view - late final ScrollController _scrollController; - - @override - void initState() { - super.initState(); - - // Create scroll controller - _scrollController = ScrollController(); - } - - @override - void dispose() { - _scrollController.dispose(); // Dispose the ScrollController - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // Derive whether window is narrow - isNarrow = WindowSize().isNarrowWindow(widget.constraints); - // Calculate the aspect radio for grid cards - cardAspectRatio = ListItemSize().calculateCardAspectRatio( - widget.constraints, - ); - - // By default _permissions is the full list of permissions - _permissions = permMapToList(widget.permDataMap); - - return Expanded( - child: GridView.builder( - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - // Aspect ratio calculated from LayoutBuilder box constraints - crossAxisCount: 1, - childAspectRatio: cardAspectRatio, - ), - padding: const EdgeInsets.all(10), - itemCount: _permissions.length, //widget.permDataMap.length, - itemBuilder: (context, index) => Card( - child: Center( - child: Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - child: MarkdownTooltip( - message: _permissions[index].toolTip, - child: ListTile( - // Leading icon denoting agreement of access terms - leading: SizedBox( - width: ListIconSize.width, - child: Center( - child: Ink( - padding: const EdgeInsets.all(8), - decoration: listIconShape, - child: const Icon(Icons.handshake), - ), - ), - ), - // Permission item title - title: Text( - _permissions[index].recipientName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - 'Recipient type: ${_permissions[index].recipientType} \n' - 'WebId: ${_permissions[index].recipientWebId} \n' - 'Permissions: ${_permissions[index].permList.join(', ')}', - maxLines: 4, // Limit to 4 lines - overflow: TextOverflow.ellipsis, - ), - // Show revoke button for recipientWebId != ownerWebId - trailing: (widget.ownerWebId != - _permissions[index].recipientWebId) - ? SizedBox( - height: ListIconSize.height, - width: ListIconSize.twoIconWidth, - child: RevokePermissionButton( - resourceName: widget.resourceName, - permDataMap: widget.permDataMap, - receiverWebId: _permissions[index].recipientWebId, - ownerWebId: widget.ownerWebId, - granterWebId: widget.granterWebId, - isFile: widget.isFile, - isExternalRes: widget.isExternalRes, - updatePermissionsFunction: - widget.updatePermissionsFunction, - ), - ) - : null, - ), - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/solid/revoke_permission_button.dart b/lib/src/solid/revoke_permission_button.dart deleted file mode 100644 index 06de114f..00000000 --- a/lib/src/solid/revoke_permission_button.dart +++ /dev/null @@ -1,186 +0,0 @@ -/// A button for revoking permission. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart' show ActionColors; - -import 'package:solidpod/src/solid/constants/common.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/revoke_permission.dart'; -import 'package:solidpod/src/solid/utils/snack_bar.dart'; - -/// A [StatefulWidget] for the revoke permission icon button. Updates -/// owner's ACL for resource, updates owner, granter, recipient logs, -/// and calls updatePermissions() to refresh permission table data. -/// -/// Parameters: -/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource. -/// - [permDataMap] is the map of permission data for the [resourceName] -/// - [ownerWebId] - WebId of the owner of the resource. Required if the resource is externally owned. -/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externally owned. -/// - [receiverWebId] - WebId with access to the resource, one of ownerWebId, granterWebId or recipientWebId. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [isExternalRes] - Boolean flag describing whether the resource -/// is externally owned. -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// - -class RevokePermissionButton extends StatefulWidget { - /// The name of the file or directory for which permissions are being - /// shown. - - final String resourceName; - - /// Map of access permission data being displayed for [resourceName]. - - final Map permDataMap; - - /// WebId with access to resource. - - final String receiverWebId; - - /// WebId of the resource owner. - - final String ownerWebId; - - /// WebId of the user granting/revoking access to the resource. - - final String granterWebId; - - /// A flag denoting whether resource is externally owned. - - final bool isExternalRes; - - /// A flag to determine whether the given resource is a file or not. - - final bool isFile; - - /// Function run to update permissions table - - final Function updatePermissionsFunction; - - const RevokePermissionButton({ - super.key, - required this.resourceName, - required this.permDataMap, - required this.receiverWebId, - required this.ownerWebId, - required this.granterWebId, - required this.updatePermissionsFunction, - required this.isFile, - this.isExternalRes = false, - }); - - @override - State createState() => _RevokePermissionButtonState(); -} - -class _RevokePermissionButtonState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return MarkdownTooltip( - message: 'Revoke all access to this recipient', - child: IconButton( - icon: const Icon( - Icons.delete, - size: 24.0, - color: ActionColors.delete, - ), - onPressed: () { - showDialog( - context: context, - builder: (ctx) { - return AlertDialog( - title: const Text('Please Confirm'), - content: Text( - 'Are you sure you want to remove the [${(widget.permDataMap[widget.receiverWebId][permStr] as List).join(', ')}] permission/s from ${widget.receiverWebId.replaceAll('.ttl', '')}?', - ), - actions: [ - // The "Yes" button - TextButton( - onPressed: () async { - await revokePermission( - fileName: widget.resourceName, - isFile: widget.isFile, - permissionList: widget.permDataMap[widget.receiverWebId] - [permStr] as List, - recipientIndOrGroupWebId: widget.receiverWebId, - ownerWebId: widget.ownerWebId, - granterWebId: widget.granterWebId, - recipientType: getRecipientType( - widget.permDataMap[widget.receiverWebId][agentStr] - as String, - widget.receiverWebId, - ), - isExternalRes: widget.isExternalRes, - ); - - if (ctx.mounted) { - Navigator.pop(ctx); - } - if (ctx.mounted) { - showSnackBar( - context, - 'Permission revoked successfully!', - ActionColors.success, - ); - } - await widget.updatePermissionsFunction( - widget.resourceName, - isFile: widget.isFile, - ); - }, - child: const Text('Yes'), - ), - TextButton( - onPressed: () { - // Close the dialog - Navigator.of(ctx).pop(); - }, - child: const Text('No'), - ), - ], - ); - }, - ); - }, - ), - ); - } -} diff --git a/lib/src/solid/select_recipients.dart b/lib/src/solid/select_recipients.dart deleted file mode 100644 index ff33a93f..00000000 --- a/lib/src/solid/select_recipients.dart +++ /dev/null @@ -1,181 +0,0 @@ -/// Button list for selecting recipients. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; - -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission_helper.dart'; - -/// A [StatefulWidget] for a container of buttons for -/// selecting recipients in the grant permission form. -/// -/// Parameters: -/// - [isExternalRes] - Boolean flag describing whether -/// the resource is externally owned. -/// - [recipientTypeList] - List of recipient type options to show. -/// - [setPublicFunction] - a function for setting recipients to -/// the public. -/// - [setAuthUsersFunction] - a function for setting recipients -/// to all authorised users. -/// - [setIndividualFunction] - a function for selecting an -/// individual recipient webId. -/// - [setGroupFunction] - a function for selecting a -/// group of webIds as recipients. - -class SelectRecipients extends StatefulWidget { - /// A flag denoting whether the resource is externally owned. - - final bool isExternalRes; - - /// The list of types of recipients to show in form. By default - /// all four types of recipient are listed. - - final List recipientTypeList; - - /// A function for setting recipients to the public. - - final Function setPublicFunction; - - /// A function for setting recipients to all authorised users. - - final Function setAuthUsersFunction; - - /// A function for selecting an individual recipient webId. - - final Function setIndividualFunction; - - /// A function for selecting a group of webIds as recipients. - - final Function setGroupFunction; - - const SelectRecipients({ - super.key, - required this.isExternalRes, - required this.recipientTypeList, - required this.setPublicFunction, - required this.setAuthUsersFunction, - required this.setIndividualFunction, - required this.setGroupFunction, - }); - - @override - State createState() => _SelectRecipientsState(); -} - -class _SelectRecipientsState extends State { - /// Define recipient type list - - List recipientTypeList = []; - - /// Selected recipient type - - RecipientType? _selectedRecipientType; - - // Allowed recipient types - - List allowedRecipientTypes = []; - - @override - void initState() { - super.initState(); - - // Load recipient type list to be displayed - for (final recTypeStr in widget.recipientTypeList) { - recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); - } - - // jesscmoore 20260118: requires check grant/revoke to - // public/auth works on external resources - // av 20250526: - // Public and Authenticated recipient buttons are - // disabled currently because - // providing public or authenticated permissions to - // external resources is not yet implemented in - // [grantPermission()] function. - widget.isExternalRes - ? allowedRecipientTypes = granterRecipientTypes - : allowedRecipientTypes = ownerRecipientTypes; - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - RadioGroup( - groupValue: _selectedRecipientType, - onChanged: (RecipientType? value) { - setState(() { - _selectedRecipientType = value; - }); - switch (value) { - case RecipientType.public: - widget.setPublicFunction(); - case RecipientType.authUser: - widget.setAuthUsersFunction(); - case RecipientType.individual: - widget.setIndividualFunction(); - case RecipientType.group: - widget.setGroupFunction(); - case RecipientType.none: - return; - case null: - return; - } - }, - child: Column( - children: [ - for (final rtype in ownerRecipientTypes) - if (recipientTypeList.contains(rtype)) - MarkdownTooltip( - message: recipientToolTips[rtype]!, - child: ListTile( - title: Text(rtype.description), - leading: Radio(value: rtype), - ), - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/src/solid/share_resource_button.dart b/lib/src/solid/share_resource_button.dart deleted file mode 100644 index c127ea26..00000000 --- a/lib/src/solid/share_resource_button.dart +++ /dev/null @@ -1,218 +0,0 @@ -/// A button for sharing a resource. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' show SharingPageLayout; - -import 'package:solidpod/src/solid/grant_permission_form.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; - -/// A [StatefulWidget] for sharing a resource, by either creating -/// an access permission for a new recipient or updating the access -/// permission of an existing recipient. -/// -/// Parameters: -/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource. -/// - [fileNameController] - The [TextEditingController] for the filename -/// field. -/// - [isExternalRes] - Boolean flag describing whether the resource -/// is externally owned. -/// - [ownerWebId] - WebId of the owner of the resource. Required if the resource is externally owned. -/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externally owned. -/// - [accessModeList] - List of access mode options to show. -/// - [recipientTypeList] - List of recipient type options to show. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// -/// - [onPermissionGranted] - Callback function called when permissions are granted successfully. - -class ShareResourceButton extends StatefulWidget { - final TextEditingController fileNameController; - - /// String to assign the webId of the resource owner. - - final String ownerWebId; - - /// String to assign the external webId of the resource granter. - - final String granterWebId; - - /// The name of the file or directory that access is being granted for. - - final String? resourceName; - - final bool isExternalRes; - - /// A flag to determine whether the given resource is a file or not. - - final bool isFile; - - /// The list of access modes to be displayed. By default all four types of - /// access mode are listed. - - final List accessModeList; - - /// The list of types of recipients receiving permission to access the resource. By default all four - /// types of recipient are listed. - - final List recipientTypeList; - - /// Map of data files on a user's POD used to extract the - /// user's recipient list by the WebIdTextInputScreen. - /// If not provided, the WebIdTextInputScreen will read the - /// user's files in their app data folder on their Pod to - /// fetch the ACLs needed to derive the user's recipient list. - - final Map dataFilesMap; - - /// Function run to update permissions table - - final Function updatePermissionsFunction; - - /// Callback function called when permissions are granted successfully. - - final VoidCallback? onPermissionGranted; - - const ShareResourceButton({ - super.key, - required this.fileNameController, - required this.updatePermissionsFunction, - this.resourceName, - required this.ownerWebId, - required this.granterWebId, - this.accessModeList = const ['read', 'write', 'append', 'control'], - this.recipientTypeList = const ['public', 'indi', 'auth', 'group'], - required this.isExternalRes, - required this.isFile, - this.dataFilesMap = const {}, - this.onPermissionGranted, - }); - - @override - State createState() => _ShareResourceButtonState(); -} - -class _ShareResourceButtonState extends State { - /// Filename text controller - - late final TextEditingController _fileNameController; - - /// Owner WebId - - late final String _ownerWebId; - - /// Granter WebId - - late final String _granterWebId; - - /// Selected resource - assigned on Share Resource button press - - String _resourceName = ''; - - /// A flag to identify if the resource is a file or not - - bool isFile = true; - - /// Flag to track if permissions were granted successfully. - - bool permissionsGrantedSuccessfully = false; - - @override - void initState() { - super.initState(); - - _fileNameController = widget.fileNameController; - _ownerWebId = widget.ownerWebId; - _granterWebId = widget.granterWebId; - } - - @override - void dispose() { - _fileNameController.dispose(); // Dispose filename editing controller - super.dispose(); - } - - /// Mark permissions as granted successfully for callback tracking - Future _updatePermissionGrantedStatus() async { - setState(() => permissionsGrantedSuccessfully = true); - } - - /// Private function to call alert dialog in share resource button - /// context. This provides an alert dialog over the top of the - /// grant permission form dialog. - Future _alert(String msg) async => alert(context, msg); - - // Resource is a file if resource selected in GrantPermissionUi() - bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; - - @override - Widget build(BuildContext context) { - return Padding( - padding: SharingPageLayout.inputPadding, - child: ElevatedButton.icon( - icon: const Icon(Icons.share), - onPressed: () async { - // Assign dataFile if null (first Grant press) - _resourceName = widget.resourceName ?? _fileNameController.text; - - if (_resourceName != '') { - // Display GrantPermissionForm dialog to enter - // recipient and access modes - await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return GrantPermissionForm( - resourceName: _resourceName, - accessModeList: widget.accessModeList, - recipientTypeList: widget.recipientTypeList, - updatePermissionsFunction: widget.updatePermissionsFunction, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - isExternalRes: widget.isExternalRes, - isFile: _getIsFile(), - dataFilesMap: widget.dataFilesMap, - updatePermissionGrantedFunction: - _updatePermissionGrantedStatus, - onPermissionGranted: widget.onPermissionGranted, - ); - }, - ); - } else { - await _alert('Please select one or more recipients'); - } - }, - label: const Text('Share Resource'), - ), - ); - } -} diff --git a/lib/src/solid/shared_resources_ui.dart b/lib/src/solid/shared_resources_ui.dart deleted file mode 100644 index ec6b711c..00000000 --- a/lib/src/solid/shared_resources_ui.dart +++ /dev/null @@ -1,195 +0,0 @@ -/// A screen to demonstrate the data sharing capabilities of PODs. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' - show - normalLoadingScreenHeight, - smallGapV, - largeGapV, - makeHeading, - makeSubHeading; - -import 'package:solidpod/src/solid/shared_resources.dart'; -import 'package:solidpod/src/solid/solid_func_call_status.dart'; -import 'package:solidpod/src/solid/utils/authdata_manager.dart'; -import 'package:solidpod/src/widgets/app_bar.dart'; -import 'package:solidpod/src/widgets/loading_screen.dart'; -import 'package:solidpod/src/widgets/shared_resources_table.dart'; - -/// A widget for the demonstration screen of the application. - -class SharedResourcesUi extends StatefulWidget { - /// Initialise widget variables. - - const SharedResourcesUi({ - required this.child, - this.title = 'Demonstrating retrieve shared data functionality', - this.backgroundColor = const Color.fromARGB(255, 210, 210, 210), - this.showAppBar = true, - this.sourceWebId, - this.fileName, - this.customAppBar, - super.key, - }); - - /// The child widget to return to when back button is pressed. - final Widget child; - - /// The text appearing in the app bar. - final String title; - - /// The text appearing in the app bar. - final Color backgroundColor; - - /// The boolean to decide whether to display an app bar or not - final bool showAppBar; - - /// The webId of the owner of a resource. This is a non required - /// parameter. If not set UI will display all resources - final String? sourceWebId; - - /// The name of the resource being shared. This is a non required - /// parameter. If not set UI will display all resources - final String? fileName; - - /// App specific app bar. If not set default app bar will be displayed. - final PreferredSizeWidget? customAppBar; - - @override - SharedResourcesUiState createState() => SharedResourcesUiState(); -} - -/// Class to build a UI for granting permission to a given file -class SharedResourcesUiState extends State - with SingleTickerProviderStateMixin { - /// Permission data map of a file - Map permDataMap = {}; - - @override - void initState() { - super.initState(); - } - - /// Build the main widget - Widget _buildSharedResourcePage( - BuildContext context, - List? futureObjList, - ) { - // Build the widget. - - var sharedResMap = {}; - if (futureObjList != null) { - sharedResMap = futureObjList.first as Map; - } - - const welcomeHeadingStr = 'Resources shared with you'; - - var subHeadingStr = widget.fileName != null - ? 'Filtered by the ${widget.fileName} file' - : 'No filters'; - - subHeadingStr = widget.sourceWebId != null - ? subHeadingStr.contains('Filtered by') - ? '$subHeadingStr and the WebID ${widget.sourceWebId}' - : 'Filtered by the WebID ${widget.sourceWebId}' - : subHeadingStr; - - return Scaffold( - appBar: (!widget.showAppBar) - ? null - : (widget.customAppBar != null) - ? widget.customAppBar - : defaultAppBar( - context, - widget.title, - widget.backgroundColor, - widget.child, - ), - body: SingleChildScrollView( - child: Column( - children: [ - smallGapV, - Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - children: [ - makeHeading(welcomeHeadingStr, addPadding: false), - smallGapV, - Column( - mainAxisSize: MainAxisSize.min, - children: [ - largeGapV, - makeSubHeading(subHeadingStr), - buildSharedResourcesTable( - context, - sharedResMap, - widget.child, - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - // Build widget with a Future - final fileName = widget.fileName != null ? widget.fileName as String : null; - final sourceWebId = - widget.sourceWebId != null ? widget.sourceWebId as String : null; - return FutureBuilder( - future: Future.wait([ - sharedResources(fileName, sourceWebId), - AuthDataManager.getWebId(), - ]), - builder: (context, snapshot) { - if (snapshot.hasData) { - if (snapshot.data!.first == SolidFunctionCallStatus.notLoggedIn) { - return widget.child; - } else { - return _buildSharedResourcePage(context, snapshot.data); - } - } else { - return Scaffold(body: loadingScreen(normalLoadingScreenHeight)); - } - }, - ); - } -} diff --git a/lib/src/solid/show_selected_recipients.dart b/lib/src/solid/show_selected_recipients.dart deleted file mode 100644 index 235328fd..00000000 --- a/lib/src/solid/show_selected_recipients.dart +++ /dev/null @@ -1,108 +0,0 @@ -/// A widget for showing selected recipients in the grant permission form. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' show smallGapV, RecipientTextStyle; - -import 'package:solidpod/src/solid/constants/web_acl.dart'; - -/// A [StatelessWidget] for showing selected recipients in the -/// grant permission form. -/// -/// Parameters: -/// - [selectedRecipientType] - Selected type of recipient/s. -/// - [selectedRecipientDetails] - Details of selected recipient/s. -/// - [selectedGroupName] - Name of group, if selected group of recipients. - -class ShowSelectedRecipients extends StatelessWidget { - const ShowSelectedRecipients({ - super.key, - required this.selectedRecipientType, - required this.selectedRecipientDetails, - this.selectedGroupName, - }); - - /// Selected recipient - - final RecipientType selectedRecipientType; - - /// Selected recipient details - - final String selectedRecipientDetails; - - /// Selected group name - - final String? selectedGroupName; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - children: [ - Text( - (selectedRecipientType == RecipientType.individual) - ? 'Recipient: ' - : 'Recipients: ', - style: RecipientTextStyle.label, - ), - Flexible( - // Show recipients if selected - child: Text( - selectedRecipientDetails, - style: RecipientTextStyle.webId, - ), - ), - ], - ), - if (selectedRecipientType == RecipientType.group) ...[ - smallGapV, - Row( - children: [ - const Text('Group name: ', style: RecipientTextStyle.label), - Flexible( - child: Text( - selectedGroupName!, - style: RecipientTextStyle.webId, - ), - ), - ], - ), - ], - ], - ), - ); - } -} diff --git a/lib/src/solid/utils/alert.dart b/lib/src/solid/utils/alert.dart deleted file mode 100644 index 7ce34bbf..00000000 --- a/lib/src/solid/utils/alert.dart +++ /dev/null @@ -1,53 +0,0 @@ -/// Show an Alert dialog -/// -/// Copyright (C) 2024, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Dawei Chen - -library; - -import 'package:flutter/material.dart'; - -/// Alert widget to popup a dialog with a given message -/// and an optional title -Future alert( - BuildContext context, - String msg, [ - String title = 'Notice', -]) async { - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(title), - content: Text(msg), - actions: [ - ElevatedButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); -} diff --git a/lib/src/solid/utils/is_phone.dart b/lib/src/solid/utils/is_phone.dart deleted file mode 100644 index ecff1ef6..00000000 --- a/lib/src/solid/utils/is_phone.dart +++ /dev/null @@ -1,54 +0,0 @@ -/// Check if we are running a desktop (and not a browser). -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Jess Moore - -library; - -import 'package:flutter/foundation.dart' show kIsWeb; - -import 'package:universal_io/io.dart' show Platform; - -/// Checks the platform type to determine whether running on -/// a mobile device. -bool isPhone() { - /// Returns true if running on iOS or Android - /// Returns false on Flutter Web or desktop platforms - - if (kIsWeb) { - return false; - } - - if (Platform.isIOS || Platform.isAndroid) { - return true; - } else { - return false; - } -} - -// coverage:ignore-end diff --git a/lib/src/solid/utils/push_replacement.dart b/lib/src/solid/utils/push_replacement.dart deleted file mode 100644 index 3b6cafe1..00000000 --- a/lib/src/solid/utils/push_replacement.dart +++ /dev/null @@ -1,43 +0,0 @@ -/// Utility function for navigation. -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Anushka Vidanage, Dawei Chen - -library; - -import 'package:flutter/material.dart'; - -Future pushReplacement( - BuildContext context, - Widget destinationWidget, -) async { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => destinationWidget), - (Route route) => - false, // This predicate ensures all previous routes are removed - ); -} diff --git a/lib/src/solid/utils/snack_bar.dart b/lib/src/solid/utils/snack_bar.dart deleted file mode 100644 index 28c6445a..00000000 --- a/lib/src/solid/utils/snack_bar.dart +++ /dev/null @@ -1,48 +0,0 @@ -/// A default app bar. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Dawei Chen, Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -/// A snack bar to show a custom message on screen -/// A customised background color and duration can be used -void showSnackBar( - BuildContext context, - String msg, - Color bgColor, { - Duration duration = const Duration(seconds: 4), -}) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(msg), backgroundColor: bgColor, duration: duration), - ); -} diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart deleted file mode 100644 index 6a174969..00000000 --- a/lib/src/widgets/app_bar.dart +++ /dev/null @@ -1,75 +0,0 @@ -/// A default app bar. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage, Ashley Tang - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidpod/src/solid/utils/push_replacement.dart'; - -/// A default app bar that is used when user does not define an app bar for -/// the UI -PreferredSizeWidget defaultAppBar( - BuildContext context, - String title, - Color backgroundColor, - Widget child, { - VoidCallback? onNavigateBack, - bool Function()? getResult, -}) { - return AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black), - onPressed: () { - // Call the callback if provided. - - onNavigateBack?.call(); - - if (getResult != null) { - // Pop with result from callback. - - Navigator.pop(context, getResult()); - } else { - // Use the original pushReplacement behaviour. - - pushReplacement(context, child); - - // Navigator.pushReplacement( - // context, - // MaterialPageRoute(builder: (context) => child), - // ); - } - }, - ), - backgroundColor: backgroundColor, - title: Text(title), - ); -} diff --git a/lib/src/widgets/file_explorer.dart b/lib/src/widgets/file_explorer.dart deleted file mode 100644 index da863456..00000000 --- a/lib/src/widgets/file_explorer.dart +++ /dev/null @@ -1,434 +0,0 @@ -/// A simple file explorer for navigating through both own and external PODs -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage -/// -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' show normalLoadingScreenHeight; - -import 'package:solidpod/src/solid/api/rest_api.dart'; -import 'package:solidpod/src/solid/read_external_pod.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; -import 'package:solidpod/src/solid/utils/exceptions.dart'; -import 'package:solidpod/src/solid/utils/snack_bar.dart'; -import 'package:solidpod/src/solid/write_external_pod.dart'; -import 'package:solidpod/src/widgets/loading_screen.dart'; - -/// A simple file explorer class with two input parameters -class FileExplorerScreen extends StatefulWidget { - const FileExplorerScreen({ - super.key, - required this.folderPath, - required this.child, - required this.isEditable, - required this.ownerWebId, - }); - - @override - State createState() => _FileExplorerScreenState(); - - final String folderPath; - final Widget child; - final bool isEditable; - final String ownerWebId; -} - -class _FileExplorerScreenState extends State { - String currentPath = ''; - List folderList = []; - List fileList = []; - - bool isLoading = true; - String errMsg = ''; - - @override - void initState() { - super.initState(); - _getResources(); - } - - Future _getResources() async { - try { - final res = await getResourcesInContainer(widget.folderPath); - setState(() { - folderList = res.subDirs; - fileList = res.files; - currentPath = widget.folderPath; - isLoading = false; - }); - } on AccessForbiddenException catch (e) { - debugPrint('Exception occured: $e'); - setState(() { - errMsg = 'You do not have access to this directory'; - isLoading = false; - }); - } on AccessFailedException catch (e) { - debugPrint('Exception occured: $e'); - setState(() { - errMsg = 'Reading directory content failed. Please try again'; - isLoading = false; - }); - } catch (e) { - debugPrint('Unknown exception occured: $e'); - setState(() { - errMsg = 'Unkown error occured: $e'; - isLoading = false; - }); - } - } - - PreferredSizeWidget defaltAppBar() { - return AppBar( - title: const Text('Go back'), - leading: currentPath.isNotEmpty - ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - // final parent = Directory(currentPath).parent; - // _loadDirectory(parent.path); - - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => widget.child), - ); - }, - ) - : null, - ); - } - - @override - Widget build(BuildContext context) { - if (isLoading) { - return Scaffold(body: loadingScreen(normalLoadingScreenHeight)); - } - - if (errMsg.isNotEmpty) { - return Scaffold( - appBar: defaltAppBar(), - body: Center( - child: Text( - errMsg, - style: const TextStyle(fontSize: 16, color: Colors.grey), - ), - ), - ); - } - - final List<_Section> sections = []; - - if (folderList.isNotEmpty) { - sections.add( - _Section(title: 'Folders', items: folderList, isFolder: true), - ); - } - - if (fileList.isNotEmpty) { - sections.add(_Section(title: 'Files', items: fileList, isFolder: false)); - } - - // If both are empty, show placeholder - if (sections.isEmpty) { - return Scaffold( - appBar: defaltAppBar(), - body: const Center( - child: Text( - 'No directories or files found.', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ), - ); - } - - return Scaffold( - appBar: defaltAppBar(), - body: // One scrollable list - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - color: Colors.grey[200], - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Row( - children: [ - const Icon(Icons.folder_open, color: Colors.blue), - const SizedBox(width: 8), - Expanded( - child: Text( - currentPath, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - ), - overflow: TextOverflow.ellipsis, // truncate if long - ), - ), - ], - ), - ), - Expanded( - child: ListView.builder( - itemCount: sections.length, - itemBuilder: (context, sectionIndex) { - final section = sections[sectionIndex]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Section header - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - section.title, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ), - // List items - ListView.builder( - itemCount: section.items.length, - shrinkWrap: true, - physics: - const NeverScrollableScrollPhysics(), // Disable inner scroll - itemBuilder: (context, index) { - final item = section.items[index]; - return ListTile( - leading: Icon( - section.isFolder - ? Icons.folder - : Icons.insert_drive_file, - color: - section.isFolder ? Colors.amber : Colors.blue, - ), - trailing: (!section.isFolder && widget.isEditable) - ? IconButton( - icon: const Icon(Icons.edit), - onPressed: () async { - final filePath = - '${widget.folderPath}$item'; - final fileContent = await readExternalPod( - filePath, - ); - - final TextEditingController editController = - TextEditingController( - text: fileContent, - ); - - if (!context.mounted) return; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text( - 'Edit File Content', - ), - content: SizedBox( - width: double.maxFinite, - child: TextField( - controller: editController, - autofocus: true, - maxLines: - null, // allows multiple lines - minLines: - 3, // starts with 3 visible lines - textInputAction: - TextInputAction.newline, - keyboardType: - TextInputType.multiline, - decoration: const InputDecoration( - labelText: - 'Enter your file content', - border: OutlineInputBorder(), - alignLabelWithHint: true, - ), - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of( - context, - ).pop(); // Cancel -> close dialog - }, - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () async { - final newContent = - editController.text; - // Check if the content has changed - if (newContent != fileContent) { - try { - await writeExternalPod( - filePath, - newContent, - widget.ownerWebId, - ); - - if (!context.mounted) { - return; - } - - showSnackBar( - context, - 'Changes saved successfully!', - Colors.green, - ); - } on Object catch (e, trace) { - debugPrint('$e'); - debugPrint('$trace'); - if (!context.mounted) { - return; - } - showSnackBar( - context, - 'Something went wrong! Please try again.', - Colors.red, - ); - } - } else { - if (!context.mounted) return; - showSnackBar( - context, - 'Content has not changed', - Colors.orange, - ); - } - - Navigator.of( - context, - ).pop(); // Save -> return value - }, - child: const Text('Save'), - ), - ], - ); - }, - ); - }, - ) - : null, - title: Text(item), - onTap: () async { - if (section.isFolder) { - final newFolderPath = - '${widget.folderPath}$item/'; - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => FileExplorerScreen( - folderPath: newFolderPath, - isEditable: widget.isEditable, - ownerWebId: widget.ownerWebId, - child: FileExplorerScreen( - folderPath: widget.folderPath, - isEditable: widget.isEditable, - ownerWebId: widget.ownerWebId, - child: widget.child, - ), - ), - ), - ); - } else { - final filePath = '${widget.folderPath}$item'; - - try { - final fileContent = await readExternalPod( - filePath, - ); - - if (!context.mounted) return; - await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('File content'), - content: Stack( - alignment: Alignment.center, - children: [ - Container( - width: double.infinity, - height: 300, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 15, - ), - ), - child: Text(fileContent), - ), - ], - ), - actions: [ - TextButton( - onPressed: () { - // Close the dialog - Navigator.of(ctx).pop(); - }, - child: const Text('Ok'), - ), - ], - ), - ); - } on Object catch (e, trace) { - debugPrint(e.toString()); - debugPrint(trace.toString()); - if (!context.mounted) return; - await alert( - context, - 'The file $item could not be found!', - ); - } - } - // handle open file/folder - }, - ); - }, - ), - ], - ); - }, - ), - ), - ], - ), - ); - } -} - -// Helper class for sections -class _Section { - final String title; - final List items; - final bool isFolder; - - _Section({required this.title, required this.items, required this.isFolder}); -} diff --git a/lib/src/widgets/group_webid_input.dart b/lib/src/widgets/group_webid_input.dart deleted file mode 100644 index 63b69cbb..00000000 --- a/lib/src/widgets/group_webid_input.dart +++ /dev/null @@ -1,168 +0,0 @@ -/// A dialog to input individual WebID. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart' - show smallGapV, makeSubHeading, GrantPermFormLayout; - -import 'package:solidpod/src/solid/api/rest_api.dart'; -import 'package:solidpod/src/solid/constants/common.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; - -/// A [StatefulWidget] dialog for entering group of webIds. -/// -/// Parameters: -/// - [onSubmitFunction] - function to be called on submit. - -class GroupWebIdTextInput extends StatefulWidget { - /// Function run on Submit button press. - final Function onSubmitFunction; - - const GroupWebIdTextInput({super.key, required this.onSubmitFunction}); - - @override - State createState() => _GroupWebIdTextInputState(); -} - -class _GroupWebIdTextInputState extends State { - /// Text controller for webId list field - final formControllerGroupWebIds = TextEditingController(); - - /// Text controller for group name for webId list - - final formControllerGroupName = TextEditingController(); - - @override - void initState() { - super.initState(); - } - - // dispose text controller when the widget is unmounted - @override - void dispose() { - formControllerGroupWebIds.dispose(); - formControllerGroupName.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - smallGapV, - // Explain webId with example - MarkdownTooltip( - message: '$whatIsWebID Eg: $demoWebID', - child: makeSubHeading('Enter recipient group WebIds'), - ), - // Add padding to webid textformfield and suggestion drop down - Container( - padding: GrantPermFormLayout.inputPadding, - child: Column( - children: [ - // Group name. Should be a single string - TextFormField( - controller: formControllerGroupName, - decoration: const InputDecoration( - labelText: 'Group name', - hintText: - 'Multiple words will be combined using the symbol -', - ), - ), - smallGapV, - // List of Web IDs divided by semicolon - TextFormField( - controller: formControllerGroupWebIds, - decoration: const InputDecoration( - labelText: 'List of WebIDs', - hintText: 'Divide multiple WebIDs using the semicolon (;)', - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () async { - // Check if all the input entries are correct - final groupName = formControllerGroupName.text.trim(); - final groupWebIds = formControllerGroupWebIds.text.trim(); - - // Check if both fields are not empty - if (groupName.isNotEmpty && groupWebIds.isNotEmpty) { - final webIdList = groupWebIds.split(';'); - - // Check if all the webIds are true links - var trueWebIdsFlag = true; - for (final webId in webIdList) { - if (!Uri.parse( - webId.replaceAll('#me', ''), - ).isAbsolute || - !(await checkResourceStatus(webId) == - ResourceStatus.exist)) { - trueWebIdsFlag = false; - } - } - - if (trueWebIdsFlag) { - // Save selected webid group - widget.onSubmitFunction(groupName, webIdList); - } else { - if (!context.mounted) return; - await alert( - context, - 'At least one of the Web IDs you entered is not valid', - ); - } - } else { - if (!context.mounted) return; - await alert( - context, - 'Please enter a group name and a list of Web IDs', - ); - } - }, - child: const Text('Select Group of WebIds'), - ), - ], - ), - ], - ), - ), - ], - ); - } -} diff --git a/lib/src/widgets/ind_webid_input.dart b/lib/src/widgets/ind_webid_input.dart deleted file mode 100644 index a459306c..00000000 --- a/lib/src/widgets/ind_webid_input.dart +++ /dev/null @@ -1,256 +0,0 @@ -/// A dialog to input Group of WebIDs. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage, Jess Moore - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidui/solidui.dart' - show - smallGapV, - makeSubHeading, - GrantPermFormLayout, - WebIdLayout, - DropdownColors; - -import 'package:solidpod/src/solid/api/rest_api.dart'; -import 'package:solidpod/src/solid/constants/common.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; - -/// A [StatefulWidget] dialog for adding an individual webId. -/// Function call requires the following inputs. -/// [onSubmitFunction] is the function to be called on submit. -/// [uniqRecipWebIdList] is a list of the webIds of unique recipients of the -/// owner's data. -/// -class IndWebIdTextInput extends StatefulWidget { - /// Initialise widget variables. - - const IndWebIdTextInput({ - required this.onSubmitFunction, - this.uniqRecipWebIdList, - super.key, - }); - - /// Function run on Submit button press. - final Function onSubmitFunction; - - /// List of unique recipient webIds - final List? uniqRecipWebIdList; - - @override - State createState() => _IndWebIdTextInputState(); -} - -class _IndWebIdTextInputState extends State { - /// Text controller for WebId field - final formControllerWebId = TextEditingController(); - - /// Capture whether user has started to enter text - bool _textEntered = false; - - /// WebId list - List webIdList = []; - - /// Initialise the matching suggestions list - List suggestionList = []; - String hint = ''; - - // dispose text controller when the widget is unmounted - @override - void dispose() { - formControllerWebId.dispose(); - super.dispose(); - } - - @override - void initState() { - webIdList = widget.uniqRecipWebIdList ?? []; - super.initState(); - } - - /// Generate advice to help user enter valid WebID - String? get _helpText { - final text = formControllerWebId.value.text.trim(); - final uri = Uri.parse(text); - - // Check for https scheme and :// - if (!uri.isScheme('HTTPS') || !uri.toString().contains('://')) { - return 'Must start with https://'; - } - // Check WebID contains host followed by '/' - - if (!uri.path.contains('/')) { - return 'Must have form https://[POD server host]/[their username]/profile/card#me'; - } - // Check for WebID path with profile suffix - if (!uri.path.toLowerCase().contains('/profile/card')) { - return 'Must end with \'/[their username]/profile/card#me\''; - } - // Check ends in #me - if (!(uri.fragment.toLowerCase() == 'me')) { - return 'Must end with URL fragment #me after /profile/card'; - } - // Check fully qualified web address - // 20250721 jm Retaining this check, may not be needed - if (!Uri.parse(text.replaceAll('#me', '')).isAbsolute) { - return 'Must be a fully qualified web address'; - } - // return null if the text is valid - return null; - } - - /// Generate suggestions for users based on input matches to - /// current complete recipient list of user - void filterSuggestions(String value) { - suggestionList.clear(); - - if (value.isEmpty) { - setState(() {}); - return; - } - suggestionList = webIdList - .where((e) => e.toLowerCase().contains(value.toLowerCase())) - .toList(); - - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - smallGapV, - // Explain webId with example - MarkdownTooltip( - message: '$whatIsWebID Eg: $demoWebID', - child: makeSubHeading('Enter or select recipient\'s WebId'), - ), - // Add padding to webid textformfield and suggestion drop down - Container( - padding: GrantPermFormLayout.inputPadding, - child: Column( - children: [ - // Web ID text field - TextFormField( - controller: formControllerWebId, - decoration: InputDecoration( - labelText: 'Individual\'s webID', - // Once user has started entering text, use formfield - // error message to advise user how to specify - // valid webId - errorText: _textEntered ? _helpText : null, - ), - onFieldSubmitted: (value) {}, - onChanged: (value) => setState(() { - // User has started entering text - _textEntered = true; - // Filter suggestions - filterSuggestions(value); - }), - ), - smallGapV, - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (webIdList.isNotEmpty) ...[ - if (suggestionList.isNotEmpty || - formControllerWebId.text.isNotEmpty) ...[ - // 20250729 jm: Wrap ListView() in fixed SizeBox() to avoid render problems in AlertDialog() - boxedSuggestionList(context, suggestionList), - ] else ...[ - boxedSuggestionList(context, webIdList), - ], - ], - TextButton( - onPressed: () async { - final receiverWebId = formControllerWebId.text.trim(); - - // User has entered WebId text that satisfies error checks - if (receiverWebId.isNotEmpty && _helpText == null) { - // Check WebId exists - - if (await checkResourceStatus(receiverWebId) == - ResourceStatus.exist) { - // Save provided WebId - widget.onSubmitFunction(receiverWebId); - } else { - if (!context.mounted) return; - // Request WebId that exists - await alert( - context, - 'This WebID does not exist. Please enter the correct WebID', - ); - } - } - }, - child: const Text('Select WebId'), - ), - ], - ), - ], - ), - ), - ], - ); - } - - Flexible boxedSuggestionList(BuildContext context, List idList) { - return Flexible( - child: SizedBox( - height: WebIdLayout.dropdownHeight, - child: ListView.builder( - padding: WebIdLayout.listPadding, - itemCount: idList.length, - itemBuilder: (context, index) { - return Card( - elevation: WebIdLayout.dropdownElevation, - child: ListTile( - title: Text(idList[index]), - focusColor: DropdownColors.primary, - hoverColor: DropdownColors.accent, - splashColor: DropdownColors.primary, - onTap: () => setState(() { - // User has started entering text - _textEntered = true; - formControllerWebId.text = idList[index]; - }), - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/src/widgets/ind_webid_input_screen.dart b/lib/src/widgets/ind_webid_input_screen.dart deleted file mode 100644 index 76edc92c..00000000 --- a/lib/src/widgets/ind_webid_input_screen.dart +++ /dev/null @@ -1,127 +0,0 @@ -/// A screen to retrieve all the webids of recipients of a user's Pod files before loading the webid input dialog. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Jess Moore - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidui/solidui.dart' show normalLoadingScreenHeight; - -import 'package:solidpod/src/solid/get_recipient_list.dart'; -import 'package:solidpod/src/widgets/ind_webid_input.dart'; -import 'package:solidpod/src/widgets/loading_screen.dart'; - -/// A screen that runs before opening the WebID input dialog, which -/// retrieves the list of files in the owner's pod. -/// -/// Parameters: -/// - [onSubmitFunction] is the function to be called on submit -/// -class IndWebIdInputScreen extends StatefulWidget { - /// Initialise widget variables. - const IndWebIdInputScreen({ - required this.onSubmitFunction, - this.dataFilesMap = const {}, - super.key, - }); - - /// Function run on Submit button press. - final Function onSubmitFunction; - - /// Map of data files on a user's POD used to extract the - /// user's recipient list by the WebIdTextInputScreen. - /// If not provided, the file list must be read to obtain - /// the user's recipient list used in the WebIdTextInputScreen. - final Map dataFilesMap; - - @override - State createState() => _IndWebIdInputScreenState(); -} - -class _IndWebIdInputScreenState extends State { - /// Future comprising the unique recipient WebId list of the user's Pod data. - static Future>? _asyncGetRecipList; - - /// List of unique recipient WebId list of the user's Pod data. - List uniqRecipWebIdList = []; - - @override - void initState() { - // Retrieve files and derive unique recipient WebId list. - if (widget.dataFilesMap.isEmpty) { - _asyncGetRecipList = getRecipientList(); - } else { - // Extract unique recipient WebId list if file data provided. - uniqRecipWebIdList = extractRecipWebIdList(widget.dataFilesMap); - } - super.initState(); - } - - // Load Individual WebId Text Input - Widget _loadIndWebIdTextInput( - Function onSubmitFunction, [ - List uniqRecipWebIdList = const [], - ]) { - return IndWebIdTextInput( - onSubmitFunction: onSubmitFunction, - uniqRecipWebIdList: uniqRecipWebIdList, - ); - } - - @override - Widget build(BuildContext context) { - return (widget.dataFilesMap.isNotEmpty) - ? _loadIndWebIdTextInput(widget.onSubmitFunction, uniqRecipWebIdList) - : FutureBuilder( - future: _asyncGetRecipList, - builder: (context, snapshot) { - Widget returnVal; - if (snapshot.connectionState == ConnectionState.done) { - return snapshot.data == null || - snapshot.data.toString() == 'null' || - snapshot.data == [] - // Load Individual WebId Input Dialog Screen without recipient list - ? returnVal = _loadIndWebIdTextInput( - widget.onSubmitFunction, - ) - // Load Individual WebId Input Dialog Screen with recipient list - : returnVal = _loadIndWebIdTextInput( - widget.onSubmitFunction, - snapshot.data!, - ); - } else { - returnVal = loadingScreen(normalLoadingScreenHeight); - } - return returnVal; - }, - ); - } -} diff --git a/lib/src/widgets/loading_screen.dart b/lib/src/widgets/loading_screen.dart deleted file mode 100644 index 9537fa06..00000000 --- a/lib/src/widgets/loading_screen.dart +++ /dev/null @@ -1,85 +0,0 @@ -/// Shows a Widget as loading screen in the process of loading. -/// -// Time-stamp: -/// -/// Copyright (C) 2024, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// Authors: Graham Williams -library; - -import 'package:flutter/material.dart'; - -/// Color variables used in initial loading screen. - -const lightBlue = Color(0xFF61B2CE); - -/// Creates a loading screen widget. -/// -/// This widget displays a centered loading indicator within a container, -/// accompanied by a text message. - -Widget loadingScreen(double height) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - alignment: AlignmentDirectional.center, - child: Container( - decoration: BoxDecoration( - color: lightBlue, - borderRadius: BorderRadius.circular(25.0), - ), - width: 300.0, - height: height, - alignment: AlignmentDirectional.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Center( - child: SizedBox( - height: 50.0, - width: 50.0, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 7.0, - ), - ), - ), - Container( - margin: const EdgeInsets.only(top: 25.0), - child: const Center( - child: Text( - 'Loading.. Please wait!', - style: TextStyle(fontSize: 20, color: Colors.white), - ), - ), - ), - ], - ), - ), - ), - ], - ); -} diff --git a/lib/src/widgets/permission_checkbox.dart b/lib/src/widgets/permission_checkbox.dart deleted file mode 100644 index 0305bda6..00000000 --- a/lib/src/widgets/permission_checkbox.dart +++ /dev/null @@ -1,63 +0,0 @@ -/// A checkbox widget for access modes. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:markdown_tooltip/markdown_tooltip.dart'; - -import 'package:solidpod/src/solid/constants/web_acl.dart'; - -/// Checkbox widget to display different access mode selections. Function call -/// requires the following inputs -/// [accessMode] is the AccessMode instance for the checkbox -/// [checkboxChecked] is the boolean controller for the checkbox press -/// [updateCheckBox] is the function to update the checkbox data when pressed -/// - -MarkdownTooltip permissionCheckbox( - AccessMode accessMode, - bool checkboxChecked, - Function updateCheckBox, -) { - return MarkdownTooltip( - message: accessMode.description, - child: CheckboxListTile( - title: Text(accessMode.mode), - value: checkboxChecked, - onChanged: (newValue) { - updateCheckBox(newValue, accessMode); - }, - controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox - ), - ); -} diff --git a/lib/src/widgets/shared_resources_table.dart b/lib/src/widgets/shared_resources_table.dart deleted file mode 100644 index 4a9f65f2..00000000 --- a/lib/src/widgets/shared_resources_table.dart +++ /dev/null @@ -1,234 +0,0 @@ -/// A table displaying permission data for a given file. -/// -// Time-stamp: -/// -/// Copyright (C) 2024-2025, Software Innovation Institute, ANU. -/// -/// Licensed under the MIT License (the "License"). -/// -/// License: https://choosealicense.com/licenses/mit/. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// -/// -/// Authors: Anushka Vidanage - -library; - -import 'package:flutter/material.dart'; - -import 'package:solidpod/src/solid/api/common_permission.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/read_external_pod.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; -import 'package:solidpod/src/solid/utils/misc.dart'; -import 'package:solidpod/src/widgets/file_explorer.dart'; - -/// Build the permission table widget. Function call requires the -/// following inputs -/// [context] is the BuildContext from which this function is called. -/// [sharedResMap] is the map containing data of shared resources. -/// [parentWidget] is the widget to return to after an action Eg: deletion of a -/// permission -/// -Widget buildSharedResourcesTable( - BuildContext context, - Map sharedResMap, - Widget parentWidget, -) { - final cWidth = MediaQuery.of(context).size.width * 0.18; - DataColumn buildDataColumn(String title, String tooltip) { - return DataColumn( - label: Expanded(child: Center(child: Text(title))), - tooltip: tooltip, - ); - } - - DataCell buildDataCell(String content) { - return DataCell( - SizedBox( - width: cWidth, - child: Column(children: [SelectableText(content)]), - ), - ); - } - - return Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: DataTable( - dataRowMaxHeight: double.infinity, - horizontalMargin: 10, - columnSpacing: 10, - columns: [ - buildDataColumn( - 'Resource URL', - 'WebID of the POD receiving permissions', - ), - buildDataColumn('Shared on', 'Shared date and time'), - buildDataColumn('Owner', 'Resource owner WebID'), - buildDataColumn('Granter', 'Permission granter WebID'), - buildDataColumn('Permissions', 'List of permissions given'), - buildDataColumn('View/Open', 'View file'), - ], - rows: sharedResMap.keys.map((index) { - return DataRow( - cells: [ - DataCell( - Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), - width: cWidth, - child: SelectableText( - index as String, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - ), - DataCell( - Row( - children: [ - Expanded( - child: Text( - getDateTime( - sharedResMap[index][PermissionLogLiteral.logtime] - as String, - ), - ), - ), - ], - ), - ), - buildDataCell( - sharedResMap[index][PermissionLogLiteral.owner] as String, - ), - buildDataCell( - sharedResMap[index][PermissionLogLiteral.granter] as String, - ), - buildDataCell( - sharedResMap[index][PermissionLogLiteral.permissions] - as String, - ), - DataCell( - isDir(index) - ? IconButton( - icon: const Icon( - Icons.folder_open_outlined, - size: 24.0, - color: Colors.blueAccent, - ), - onPressed: () async { - if (!sharedResMap[index] - [PermissionLogLiteral.permissions] - .contains('read')) { - await alert( - context, - 'You do not have read permission to this resource!', - ); - } else { - bool isEditable = [ - AccessMode.write.mode, - AccessMode.control.mode, - ].any( - (mode) => sharedResMap[index] - [PermissionLogLiteral.permissions] - .contains(mode.toLowerCase()), - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FileExplorerScreen( - folderPath: index, - isEditable: isEditable, - ownerWebId: sharedResMap[index] - [PermissionLogLiteral.owner] - as String, - child: parentWidget, - ), - ), - ); - } - }, - ) - : IconButton( - icon: const Icon( - Icons.visibility, - size: 24.0, - color: Colors.blueAccent, - ), - onPressed: () async { - try { - // Get file content - final fileContent = await readExternalPod( - index, - ); - - if (!context.mounted) return; - await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('File content'), - content: Stack( - alignment: Alignment.center, - children: [ - Container( - width: double.infinity, - height: 300, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 15, - ), - ), - child: Text(fileContent), - ), - ], - ), - actions: [ - TextButton( - onPressed: () { - // Close the dialog - Navigator.of(ctx).pop(); - }, - child: const Text('Ok'), - ), - ], - ), - ); - } on Object catch (e, trace) { - debugPrint(e.toString()); - debugPrint(trace.toString()); - if (!context.mounted) return; - await alert( - context, - 'The file $index could not be found!', - ); - } - }, - ), - ), - ], - ); - }).toList(), - ), - ), - ), - ], - ); -} diff --git a/pubspec.yaml b/pubspec.yaml index 268e36ba..0f0da2b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,22 +26,14 @@ dependencies: http: ^1.6.0 intl: ^0.20.2 jwt_decoder: ^2.0.1 - markdown_tooltip: ^0.0.10 mime: ^2.0.0 package_info_plus: ^9.0.0 path: ^1.9.1 pointycastle: ^4.0.0 rdflib: ^0.2.12 solid_auth: ^0.1.28 - solidui: ^0.1.0 universal_io: ^2.3.1 -dependency_overrides: - solidui: - git: - url: https://github.com/anusii/solidui - ref: dev - dev_dependencies: build_runner: ^2.10.5 custom_lint: ^0.8.1 @@ -51,8 +43,16 @@ dev_dependencies: file_picker: ^10.3.10 flutter_lints: ^6.0.0 import_order_lint: ^0.2.2 + markdown_tooltip: ^0.0.10 + solidui: ^0.1.0 window_manager: ^0.5.1 +dependency_overrides: + solidui: + git: + url: https://github.com/anusii/solidui.git + ref: tony/59_ui_migration + flutter: assets: - assets/images/default_image.jpg