From 9d00f5d200dec367db23be8d8c1dac6491aad799 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Fri, 16 Jan 2026 21:48:16 +1100 Subject: [PATCH 01/29] use doc strings on functions --- lib/src/solid/grant_permission_ui.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 25a9da97..a909137b 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -352,7 +352,8 @@ class GrantPermissionUiState extends State } } - // Get new permission and update the permission map + /// Get new permission and update the permission map + Future _updatePermissions( String fileName, { bool isFile = true, @@ -382,7 +383,7 @@ class GrantPermissionUiState extends State } } - // Update checkbox tick data. + /// Update checked status of access mode selections. void _updateCheckbox(bool newValue, AccessMode accessMode) => setState(() { switch (accessMode) { @@ -402,14 +403,14 @@ class GrantPermissionUiState extends State } }); - // Update individual webid input data + /// Update individual recipient webid input data void _updateIndWebIdInput(String receiverWebId) => setState(() { selectedRecipientType = RecipientType.individual; selectedRecipientDetails = receiverWebId; finalWebIdList = [receiverWebId]; }); - // Update group of webids input data + /// Update recipient group webids input data void _updateGroupWebIdInput(String groupName, List webIdList) => setState(() { selectedRecipientType = RecipientType.group; @@ -418,10 +419,10 @@ class GrantPermissionUiState extends State finalWebIdList = webIdList; }); - // Private function to call alert dialog in grant permission UI context + /// Private function to call alert dialog in grant permission UI context Future _alert(String msg) async => alert(context, msg); - // Private function to show snackbar in grant permission UI context + /// Private function to show snackbar in grant permission UI context Future _showSnackBar( String msg, Color bgColor, { From 3069be644430bb731de379556bc3043a28652fdf Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Fri, 16 Jan 2026 22:55:49 +1100 Subject: [PATCH 02/29] unwrap grantPermissionUI to see structure --- lib/src/solid/grant_permission_helper.dart | 74 --------- lib/src/solid/grant_permission_ui.dart | 171 +++++++++++++++------ 2 files changed, 121 insertions(+), 124 deletions(-) diff --git a/lib/src/solid/grant_permission_helper.dart b/lib/src/solid/grant_permission_helper.dart index 373dde94..95a3c494 100644 --- a/lib/src/solid/grant_permission_helper.dart +++ b/lib/src/solid/grant_permission_helper.dart @@ -30,7 +30,6 @@ import 'package:flutter/material.dart'; import 'package:markdown_tooltip/markdown_tooltip.dart'; -import 'package:solidpod/src/solid/constants/ui.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; import 'package:solidpod/src/widgets/permission_checkbox.dart'; @@ -256,57 +255,6 @@ Widget getRecipientText(RecipientType recipientType, String recipientDetails) => ), ); -Scrollbar getScrollbar({ - required ScrollController controller, - required Axis direction, - required Widget child, -}) => - Scrollbar( - // 20250722 jm: - // For scrollbar visibility before scrolling, - // set to true, or set property to true - // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( - // thumbVisibility: WidgetStateProperty.all(true))) - thumbVisibility: true, // show before user starts scrolling - controller: controller, - child: SingleChildScrollView( - controller: controller, - scrollDirection: direction, - child: child, - ), - ); - -Scrollbar getFormScrollbar(ScrollController controller, Widget permDataTable) => - getScrollbar( - controller: controller, - direction: Axis.horizontal, - child: Column( - children: [ - Row( - children: [ - permDataTable, - // Hspace to avoid vertical scrollbar overlap with table - ScrollbarLayout.horizontalGap, - ], - ), - // Vspace to avoid horizontal scrollbar overlap of table - ScrollbarLayout.verticalGap, - ], - ), - ); - -Scrollbar getPageScrollbar(ScrollController controller, Widget form) => - getScrollbar( - controller: controller, - direction: Axis.vertical, - child: Column( - children: [ - smallGapV, - form, - ], - ), - ); - Container getButtonContainer({required List buttons}) => Container( padding: const EdgeInsets.all(8.0), height: 100, @@ -341,25 +289,3 @@ Container getButtonContainer({required List buttons}) => Container( // } // }, // ); - -Form getForm({ - required Key formKey, - required Widget welcomeHeading, - required List children, -}) => - Form( - key: formKey, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - children: [ - welcomeHeading, - smallGapV, - Column( - mainAxisSize: MainAxisSize.min, - children: children, - ), - ], - ), - ), - ); diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index a909137b..38e8ff53 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -515,18 +515,7 @@ class GrantPermissionUiState extends State bool getIsFile() => widget.resourceName != null ? widget.isFile : isFile; - final permDataTable = buildPermDataTable( - context: context, - permDataResource: permDataFile, - isFile: getIsFile(), - permDataMap: permDataMap, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - parentWidget: widget.child, - updatePermissionsFunction: _updatePermissions, - isExternalRes: widget.isExternalRes, - ); - + // Grant Permission button final grantPermissionButton = getButton( 'Grant Permission', onPressed: () async { @@ -589,43 +578,7 @@ class GrantPermissionUiState extends State }, ); - final form = getForm( - formKey: formKey, - welcomeHeading: - buildHeading(getWelcomeStr(widget.resourceName), 22, Colors.blueGrey), - children: [ - if (widget.resourceName == null) ...[ - getResourceForm( - formController: fileNameController, - isFile: isFile, - onResourceTypeChange: (bool v) => setState(() => isFile = v), - ), - smallGapV, - retrievePermissionButton, - ], - largeGapV, - getHeading('Select the recipient/s of file access permissions'), - getRecipientText(selectedRecipientType, selectedRecipientDetails), - recipientButtonContainer, - smallGapV, - getHeading('Select the list of file access permissions'), - ...getPermissionCheckBoxes( - accessModeList, - modeSwitches: { - AccessMode.read: readChecked, - AccessMode.write: writeChecked, - AccessMode.control: controlChecked, - AccessMode.append: appendChecked, - }, - onUpdate: _updateCheckbox, - ), - grantPermissionButton, - largeGapV, - getHeading('Granted file access permissions'), - getFormScrollbar(tableScrollController, permDataTable), - ], - ); - + // Use customAppBar if provided final customAppBar = widget.customAppBar ?? defaultAppBar( context, @@ -637,11 +590,129 @@ class GrantPermissionUiState extends State ); return Scaffold( + // Display app bar if showAppBar selected + // AppBar will be defaultAppBar() if customAppBar() + // not provided appBar: widget.showAppBar ? customAppBar : null, // Make Grant Permission UI vertically scrollable // Shows when content exceeds display height - body: getPageScrollbar(pageScrollController, form), + + body: Scrollbar( + // 20250722 jm: + // For scrollbar visibility before scrolling, + // set to true, or set property to true + // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( + // thumbVisibility: WidgetStateProperty.all(true))) + thumbVisibility: true, // show before user starts scrolling + controller: pageScrollController, + child: SingleChildScrollView( + controller: pageScrollController, + scrollDirection: Axis.vertical, + child: Column( + children: [ + smallGapV, + Form( + key: formKey, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + // Welcome heading + buildHeading( + getWelcomeStr(widget.resourceName), + 22, + Colors.blueGrey, + ), + smallGapV, + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.resourceName == null) ...[ + getResourceForm( + formController: fileNameController, + isFile: isFile, + onResourceTypeChange: (bool v) => + setState(() => isFile = v), + ), + smallGapV, + retrievePermissionButton, + ], + largeGapV, + getHeading( + 'Select the recipient/s of file access permissions', + ), + getRecipientText( + selectedRecipientType, + selectedRecipientDetails, + ), + recipientButtonContainer, + smallGapV, + getHeading( + 'Select the list of file access permissions', + ), + ...getPermissionCheckBoxes( + accessModeList, + modeSwitches: { + AccessMode.read: readChecked, + AccessMode.write: writeChecked, + AccessMode.control: controlChecked, + AccessMode.append: appendChecked, + }, + onUpdate: _updateCheckbox, + ), + grantPermissionButton, + largeGapV, + getHeading('Granted file access permissions'), + // Permissions table + Scrollbar( + // 20250722 jm: + // For scrollbar visibility before scrolling, + // set to true, or set property to true + // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( + // thumbVisibility: WidgetStateProperty.all(true))) + thumbVisibility: + true, // show before user starts scrolling + controller: tableScrollController, + child: SingleChildScrollView( + controller: tableScrollController, + scrollDirection: Axis.horizontal, + child: Column( + children: [ + Row( + children: [ + buildPermDataTable( + context: context, + permDataResource: permDataFile, + isFile: getIsFile(), + permDataMap: permDataMap, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + parentWidget: widget.child, + updatePermissionsFunction: + _updatePermissions, + isExternalRes: widget.isExternalRes, + ), + // Hspace to avoid vertical scrollbar overlap with table + ScrollbarLayout.horizontalGap, + ], + ), + // Vspace to avoid horizontal scrollbar overlap of table + ScrollbarLayout.verticalGap, + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), ); } From b2468730305b51947180e0b7d2662f821edcf165 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 16:12:22 +1100 Subject: [PATCH 03/29] create GrantPermissionButton() --- lib/src/solid/grant_permission_button.dart | 257 +++++++++++++++++++++ lib/src/solid/grant_permission_ui.dart | 100 ++------ 2 files changed, 282 insertions(+), 75 deletions(-) create mode 100644 lib/src/solid/grant_permission_button.dart diff --git a/lib/src/solid/grant_permission_button.dart b/lib/src/solid/grant_permission_button.dart new file mode 100644 index 00000000..b86e6147 --- /dev/null +++ b/lib/src/solid/grant_permission_button.dart @@ -0,0 +1,257 @@ +/// A button for granting 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, Dawei Chen, Ashley Tang + +library; + +import 'package:flutter/material.dart'; + +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/solid_func_call_status.dart'; +import 'package:solidpod/src/solid/utils/alert.dart'; +import 'package:solidpod/src/solid/utils/snack_bar.dart'; + +/// A [StatefulWidget] for the grant permission button. Updates owner's +/// ACL for resource, updates owner, granter, recipient logs, +/// and calls updatePermissions() to refresh permission table data. +/// +/// Parameters: +/// - [formKey] - Key of the grant permission form. +/// - [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 externall owned. +/// - [selectedPermList] - is the list of permissions to be granted to the +/// [finalWebIdList]. +/// - [selectedRecipientType] - is the type of the recipient of recipients in the +/// [finalWebIdList]. +/// - [finalWebIdList] - is the list of webIds of the recipients +/// receiving access permissions to the file. +/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. +/// - [groupName] - Optional name of the group permission. +/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. +/// +/// - [onPermissionGranted] - Callback function called when permissions are granted successfully. + +class GrantPermissionButton extends StatefulWidget { + final GlobalKey formKey; + 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; + + final RecipientType selectedRecipientType; + final List selectedPermList; + + /// List of webIds for group permission + + final List finalWebIdList; + + /// Optional name of the group permission. + + final String? groupName; + + /// 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; + + /// Callback function called when permissions are granted successfully. + + final VoidCallback? onPermissionGranted; + + const GrantPermissionButton({ + super.key, + required this.formKey, + required this.fileNameController, + required this.selectedRecipientType, + required this.selectedPermList, + required this.finalWebIdList, + required this.updatePermissionsFunction, + this.resourceName, + required this.ownerWebId, + required this.granterWebId, + required this.isExternalRes, + required this.isFile, + this.groupName, + this.onPermissionGranted, + }); + + @override + State createState() => _GrantPermissionButtonState(); +} + +class _GrantPermissionButtonState extends State { + /// Form controller + + late final GlobalKey _formKey; + + /// Filename text controller + + late final TextEditingController _fileNameController; + + /// Selected resource - assigned once on first Grant button press + + String? dataFile; + + /// 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(); + _formKey = widget.formKey; + _fileNameController = widget.fileNameController; + } + + bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; + + /// Private function to call alert dialog in grant permission button context + Future _alert(String msg) async => alert(context, msg); + + /// Private function to show snackbar in grant permission button context + Future _showSnackBar( + String msg, + Color bgColor, { + Duration duration = const Duration(seconds: 4), + }) async => + showSnackBar(context, msg, bgColor, duration: duration); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + if (widget.selectedRecipientType.type.isNotEmpty) { + if (widget.resourceName != null) { + if (widget.selectedPermList.isNotEmpty) { + // Assign dataFile if null (first Grant press) + dataFile ??= widget.resourceName ?? _fileNameController.text; + + debugPrint('GrantPermissionButton: dataFile: $dataFile'); + debugPrint( + 'GrantPermissionButton: owner: ${widget.ownerWebId}', + ); + debugPrint( + 'GrantPermissionButton: granter: ${widget.granterWebId}', + ); + SolidFunctionCallStatus result; + try { + // Update ACL and permission logs to grant permission + result = await grantPermission( + fileName: dataFile!, + isFile: _getIsFile(), + permissionList: widget.selectedPermList, + recipientType: widget.selectedRecipientType, + recipientWebIdList: widget.finalWebIdList, + ownerWebId: widget.ownerWebId, + granterWebId: widget.granterWebId, + isExternalRes: widget.isExternalRes, + groupName: widget.groupName, + ); + } on Object catch (e, stackTrace) { + result = SolidFunctionCallStatus.fail; + debugPrintException(e, stackTrace); + } + + if (result == SolidFunctionCallStatus.success) { + _showSnackBar(successMsg, Colors.green); + // Update permissions table + await widget.updatePermissionsFunction( + dataFile, + isFile: _getIsFile(), + isExternalRes: widget.isExternalRes, + ); + + // Mark permissions as granted successfully for callback tracking + setState(() => permissionsGrantedSuccessfully = true); + + // Trigger the onPermissionGranted callback if provided + widget.onPermissionGranted?.call(); + } else if (result == SolidFunctionCallStatus.fail) { + // More detailed error message with troubleshooting tips + _showSnackBar(failureMsg, Colors.red); + + // Also log to console for debugging + debugPrintFailure( + dataFile!, + widget.finalWebIdList, + widget.selectedPermList, + ); + } else if (result == SolidFunctionCallStatus.notInitialised) { + _showSnackBar(podNotInitMsg, warnBgColor); + } else { + await _alert(updatePermissionMsg); + } + } else { + await _alert( + 'Please select one or more file access permissions', + ); + } + } else { + await _alert( + 'Please select one or more recipients', + ); + } + } else { + await _alert('Please select a type of recipient'); + } + } + }, + child: const Text('Grant Permission'), + ), + ); + } +} diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 38e8ff53..19cda8a6 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -35,14 +35,13 @@ import 'package:flutter/material.dart'; import 'package:solidpod/src/solid/chk_exists_and_has_acl.dart'; import 'package:solidpod/src/solid/constants/ui.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission.dart'; +import 'package:solidpod/src/solid/grant_permission_button.dart'; import 'package:solidpod/src/solid/grant_permission_helper.dart'; import 'package:solidpod/src/solid/read_permission.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/authdata_manager.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; -import 'package:solidpod/src/solid/utils/snack_bar.dart'; import 'package:solidpod/src/widgets/app_bar.dart'; import 'package:solidpod/src/widgets/file_permission_data_table.dart'; import 'package:solidpod/src/widgets/group_webid_input_dialog.dart'; @@ -263,7 +262,7 @@ class GrantPermissionUiState extends State /// List of webIds for group permission - List? finalWebIdList; + List finalWebIdList = []; /// Selected list of permissions @@ -359,6 +358,7 @@ class GrantPermissionUiState extends State bool isFile = true, bool isExternalRes = false, }) async { + debugPrint('_updatePermissions(): ...'); final pdata = await loadPodData( fileName, isFile: isFile, @@ -422,14 +422,6 @@ class GrantPermissionUiState extends State /// Private function to call alert dialog in grant permission UI context Future _alert(String msg) async => alert(context, msg); - /// Private function to show snackbar in grant permission UI context - Future _showSnackBar( - String msg, - Color bgColor, { - Duration duration = const Duration(seconds: 4), - }) async => - showSnackBar(context, msg, bgColor, duration: duration); - /// Build the main widget Widget _buildPermPage(BuildContext context, [List? futureObjList]) { /// Controller for vertical page scrolling @@ -515,69 +507,6 @@ class GrantPermissionUiState extends State bool getIsFile() => widget.resourceName != null ? widget.isFile : isFile; - // Grant Permission button - final grantPermissionButton = getButton( - 'Grant Permission', - onPressed: () async { - if (formKey.currentState!.validate()) { - if (selectedRecipientType.type.isNotEmpty) { - if (selectedPermList.isNotEmpty) { - final dataFile = widget.resourceName ?? fileNameController.text; - - SolidFunctionCallStatus result; - try { - result = await grantPermission( - fileName: dataFile, - isFile: getIsFile(), - permissionList: selectedPermList, - recipientType: selectedRecipientType, - recipientWebIdList: finalWebIdList as List, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - isExternalRes: widget.isExternalRes, - groupName: selectedRecipientType == RecipientType.group - ? groupNameController.text.trim() - : null, - ); - } on Object catch (e, stackTrace) { - result = SolidFunctionCallStatus.fail; - debugPrintException(e, stackTrace); - } - - if (result == SolidFunctionCallStatus.success) { - _showSnackBar(successMsg, Colors.green); - await _updatePermissions( - dataFile, - isFile: getIsFile(), - isExternalRes: widget.isExternalRes, - ); - - // Mark permissions as granted successfully for callback tracking - setState(() => permissionsGrantedSuccessfully = true); - - // Trigger the onPermissionGranted callback if provided - widget.onPermissionGranted?.call(); - } else if (result == SolidFunctionCallStatus.fail) { - // More detailed error message with troubleshooting tips - _showSnackBar(failureMsg, Colors.red); - - // Also log to console for debugging - debugPrintFailure(dataFile, finalWebIdList, selectedPermList); - } else if (result == SolidFunctionCallStatus.notInitialised) { - _showSnackBar(podNotInitMsg, warnBgColor); - } else { - await _alert(updatePermissionMsg); - } - } else { - await _alert('Please select one or more file access permissions'); - } - } else { - await _alert('Please select a type of recipient'); - } - } - }, - ); - // Use customAppBar if provided final customAppBar = widget.customAppBar ?? defaultAppBar( @@ -661,7 +590,28 @@ class GrantPermissionUiState extends State }, onUpdate: _updateCheckbox, ), - grantPermissionButton, + // Grant Permission Button - updates owner's ACL for + // resource, updates owner, granter, recipient logs, + // and calls updatePermissions() to refresh + // permission table. + GrantPermissionButton( + formKey: formKey, + fileNameController: fileNameController, + resourceName: widget.resourceName, + isFile: widget.isFile, + isExternalRes: widget.isExternalRes, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + selectedRecipientType: selectedRecipientType, + selectedPermList: selectedPermList, + finalWebIdList: finalWebIdList, + groupName: + selectedRecipientType == RecipientType.group + ? groupNameController.text.trim() + : null, + updatePermissionsFunction: _updatePermissions, + onPermissionGranted: widget.onPermissionGranted, + ), largeGapV, getHeading('Granted file access permissions'), // Permissions table From 81048e515c57e3075d10ff6520c7a170cad0634c Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 17:12:23 +1100 Subject: [PATCH 04/29] fix typo and remove debugPrints --- lib/src/solid/grant_permission_button.dart | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/src/solid/grant_permission_button.dart b/lib/src/solid/grant_permission_button.dart index b86e6147..5997cc76 100644 --- a/lib/src/solid/grant_permission_button.dart +++ b/lib/src/solid/grant_permission_button.dart @@ -51,7 +51,7 @@ import 'package:solidpod/src/solid/utils/snack_bar.dart'; /// - [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 externall owned. +/// - [granterWebId] - WebId of the granter of the resource. Required if the resource is externally owned. /// - [selectedPermList] - is the list of permissions to be granted to the /// [finalWebIdList]. /// - [selectedRecipientType] - is the type of the recipient of recipients in the @@ -180,13 +180,6 @@ class _GrantPermissionButtonState extends State { // Assign dataFile if null (first Grant press) dataFile ??= widget.resourceName ?? _fileNameController.text; - debugPrint('GrantPermissionButton: dataFile: $dataFile'); - debugPrint( - 'GrantPermissionButton: owner: ${widget.ownerWebId}', - ); - debugPrint( - 'GrantPermissionButton: granter: ${widget.granterWebId}', - ); SolidFunctionCallStatus result; try { // Update ACL and permission logs to grant permission From 1a798eb5e77b0758de198d935be77709c91dff7b Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 17:13:15 +1100 Subject: [PATCH 05/29] create RevokePermissionButton() --- lib/src/solid/revoke_permission_button.dart | 188 ++++++++++++++++++ .../widgets/file_permission_data_table.dart | 76 +------ 2 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 lib/src/solid/revoke_permission_button.dart diff --git a/lib/src/solid/revoke_permission_button.dart b/lib/src/solid/revoke_permission_button.dart new file mode 100644 index 00000000..909c41ef --- /dev/null +++ b/lib/src/solid/revoke_permission_button.dart @@ -0,0 +1,188 @@ +/// 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: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 { + /// Selected resource - assigned once on first Grant button press + + String? dataFile; + + /// A flag to identify if the resource is a file or not + + bool isFile = true; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return IconButton( + icon: const Icon( + Icons.delete, + size: 24.0, + color: Colors.red, + ), + 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!', + Colors.red, + ); + } + 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/widgets/file_permission_data_table.dart b/lib/src/widgets/file_permission_data_table.dart index 6a915865..772457ea 100644 --- a/lib/src/widgets/file_permission_data_table.dart +++ b/lib/src/widgets/file_permission_data_table.dart @@ -35,8 +35,7 @@ import 'package:flutter/material.dart' hide Key; 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'; +import 'package:solidpod/src/solid/revoke_permission_button.dart'; /// Build the permission table widget. Function call requires the /// following inputs. @@ -123,69 +122,16 @@ Widget buildPermDataTable({ // If recipient != owner, then show the delete permission button if (ownerWebId != index) ...[ DataCell( - IconButton( - icon: const Icon( - Icons.delete, - size: 24.0, - color: Colors.red, - ), - onPressed: () { - showDialog( - context: context, - builder: (ctx) { - return AlertDialog( - title: const Text('Please Confirm'), - content: Text( - 'Are you sure you want to remove the [${(permDataMap[index][permStr] as List).join(', ')}] permission/s from ${index.replaceAll('.ttl', '')}?', - ), - actions: [ - // The "Yes" button - TextButton( - onPressed: () async { - await revokePermission( - fileName: permDataResource, - isFile: isFile, - permissionList: - permDataMap[index][permStr] as List, - recipientIndOrGroupWebId: index, - ownerWebId: ownerWebId, - granterWebId: granterWebId, - recipientType: getRecipientType( - permDataMap[index][agentStr] as String, - index, - ), - isExternalRes: isExternalRes, - ); - - if (ctx.mounted) { - Navigator.pop(ctx); - } - if (ctx.mounted) { - showSnackBar( - context, - 'Permission revoked successfully!', - Colors.red, - ); - } - await updatePermissionsFunction( - permDataResource, - isFile: isFile, - ); - }, - child: const Text('Yes'), - ), - TextButton( - onPressed: () { - // Close the dialog - Navigator.of(ctx).pop(); - }, - child: const Text('No'), - ), - ], - ); - }, - ); - }, + // Revoke permissions icon button + RevokePermissionButton( + resourceName: permDataResource, + permDataMap: permDataMap, + receiverWebId: index, + ownerWebId: ownerWebId, + granterWebId: granterWebId, + isFile: isFile, + isExternalRes: isExternalRes, + updatePermissionsFunction: updatePermissionsFunction, ), ), ] else ...[ From 203d3480b2d98a6cb05be5357e9c0d6ca58860f7 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 18:00:08 +1100 Subject: [PATCH 06/29] create PermissionTable() --- lib/src/solid/grant_permission_ui.dart | 51 ++---- lib/src/solid/permission_table.dart | 228 +++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 lib/src/solid/permission_table.dart diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 19cda8a6..2bb97dac 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -37,13 +37,13 @@ import 'package:solidpod/src/solid/constants/ui.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/grant_permission_button.dart'; import 'package:solidpod/src/solid/grant_permission_helper.dart'; +import 'package:solidpod/src/solid/permission_table.dart'; import 'package:solidpod/src/solid/read_permission.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/authdata_manager.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; import 'package:solidpod/src/widgets/app_bar.dart'; -import 'package:solidpod/src/widgets/file_permission_data_table.dart'; import 'package:solidpod/src/widgets/group_webid_input_dialog.dart'; import 'package:solidpod/src/widgets/ind_webid_input_dialog.dart'; import 'package:solidpod/src/widgets/loading_screen.dart'; @@ -427,9 +427,6 @@ class GrantPermissionUiState extends State /// Controller for vertical page scrolling final pageScrollController = ScrollController(); - /// Controller for horizontal permissions table scrolling - final tableScrollController = ScrollController(); - // Check if future is set or not. If set display the permission map if (futureObjList != null && pageInitialied == false) { permDataMap = futureObjList.first as Map; @@ -615,43 +612,15 @@ class GrantPermissionUiState extends State largeGapV, getHeading('Granted file access permissions'), // Permissions table - Scrollbar( - // 20250722 jm: - // For scrollbar visibility before scrolling, - // set to true, or set property to true - // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( - // thumbVisibility: WidgetStateProperty.all(true))) - thumbVisibility: - true, // show before user starts scrolling - controller: tableScrollController, - child: SingleChildScrollView( - controller: tableScrollController, - scrollDirection: Axis.horizontal, - child: Column( - children: [ - Row( - children: [ - buildPermDataTable( - context: context, - permDataResource: permDataFile, - isFile: getIsFile(), - permDataMap: permDataMap, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - parentWidget: widget.child, - updatePermissionsFunction: - _updatePermissions, - isExternalRes: widget.isExternalRes, - ), - // Hspace to avoid vertical scrollbar overlap with table - ScrollbarLayout.horizontalGap, - ], - ), - // Vspace to avoid horizontal scrollbar overlap of table - ScrollbarLayout.verticalGap, - ], - ), - ), + PermissionTable( + resourceName: permDataFile, + permDataMap: permDataMap, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + updatePermissionsFunction: _updatePermissions, + parentWidget: widget.child, + isFile: getIsFile(), + isExternalRes: widget.isExternalRes, ), ], ), diff --git a/lib/src/solid/permission_table.dart b/lib/src/solid/permission_table.dart new file mode 100644 index 00000000..bc298e66 --- /dev/null +++ b/lib/src/solid/permission_table.dart @@ -0,0 +1,228 @@ +/// 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:solidpod/src/solid/constants/common.dart'; +import 'package:solidpod/src/solid/constants/web_acl.dart'; +import 'package:solidpod/src/solid/constants/ui.dart'; +import 'package:solidpod/src/solid/revoke_permission_button.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; + + 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, + }); + + @override + State createState() => _PermissionTableState(); +} + +class _PermissionTableState extends State { + /// Controller for horizontal permissions table scrolling + final tableScrollController = ScrollController(); + + @override + void initState() { + super.initState(); + } + + DataColumn buildDataColumn(String title, String tooltip) { + return DataColumn( + label: Expanded( + child: Center( + child: Text( + title, + ), + ), + ), + tooltip: tooltip, + ); + } + + @override + Widget build(BuildContext context) { + // Make wide permission table horizontally scrollable + // Shows when content exceeds display width + return Scrollbar( + // 20250722 jm: + // For scrollbar visibility before scrolling, + // set to true, or set property to true + // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( + // thumbVisibility: WidgetStateProperty.all(true))) + thumbVisibility: true, // show before user starts scrolling + controller: tableScrollController, + child: SingleChildScrollView( + controller: tableScrollController, + scrollDirection: Axis.horizontal, + child: Column( + children: [ + Row( + children: [ + DataTable( + columns: [ + buildDataColumn( + 'Receiver', + 'WebID of the permission recipient', + ), + buildDataColumn('Receiver type', 'Type of the receiver'), + buildDataColumn('Permissions', 'List of permissions given'), + buildDataColumn('Actions', 'Delete permission'), + ], + // receiverWebId is the webId of each individual with access to the file + rows: widget.permDataMap.keys.map((receiverWebId) { + return DataRow( + cells: [ + DataCell( + Container( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), + //width: cWidth, + child: Column( + children: [ + SelectableText( + (receiverWebId.replaceAll('.ttl', '')) + as String, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + DataCell( + Text( + getRecipientType( + widget.permDataMap[receiverWebId][agentStr] + as String, + receiverWebId as String, + ).description, + ), + ), + DataCell( + Text( + (widget.permDataMap[receiverWebId][permStr] as List) + .join(', '), + ), + ), + // If recipient != owner, then show the delete permission button + if (widget.ownerWebId != receiverWebId) ...[ + DataCell( + // Revoke permissions icon button + RevokePermissionButton( + resourceName: widget.resourceName, + permDataMap: widget.permDataMap, + receiverWebId: receiverWebId, + ownerWebId: widget.ownerWebId, + granterWebId: widget.granterWebId, + isFile: widget.isFile, + isExternalRes: widget.isExternalRes, + updatePermissionsFunction: + widget.updatePermissionsFunction, + ), + ), + ] else ...[ + const DataCell( + Text(''), + ), + ], + ], + ); + }).toList(), + ), + // Hspace to avoid vertical scrollbar overlap with table + ScrollbarLayout.horizontalGap, + ], + ), + // Vspace to avoid horizontal scrollbar overlap of table + ScrollbarLayout.verticalGap, + ], + ), + ), + ); + } +} From eac0f1cd102c72680cd8fae9fda12aef20c09ac8 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 18:00:29 +1100 Subject: [PATCH 07/29] remove unused vars --- lib/src/solid/revoke_permission_button.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/src/solid/revoke_permission_button.dart b/lib/src/solid/revoke_permission_button.dart index 909c41ef..d0653864 100644 --- a/lib/src/solid/revoke_permission_button.dart +++ b/lib/src/solid/revoke_permission_button.dart @@ -104,14 +104,6 @@ class RevokePermissionButton extends StatefulWidget { } class _RevokePermissionButtonState extends State { - /// Selected resource - assigned once on first Grant button press - - String? dataFile; - - /// A flag to identify if the resource is a file or not - - bool isFile = true; - @override void initState() { super.initState(); From 6ade070706ba3dc4f4795b1bb2cc6c43e869f518 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 18:07:07 +1100 Subject: [PATCH 08/29] remove redundant file --- .../widgets/file_permission_data_table.dart | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 lib/src/widgets/file_permission_data_table.dart diff --git a/lib/src/widgets/file_permission_data_table.dart b/lib/src/widgets/file_permission_data_table.dart deleted file mode 100644 index 772457ea..00000000 --- a/lib/src/widgets/file_permission_data_table.dart +++ /dev/null @@ -1,146 +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, Jess Moore - -library; - -import 'package:flutter/material.dart' hide Key; - -import 'package:solidpod/src/solid/constants/common.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/revoke_permission_button.dart'; - -/// Build the permission table widget. Function call requires the -/// following inputs. -/// -/// Parameters: -/// - [context] is the BuildContext from which this function is called. -/// - [permDataResource] is the name of the file or directory for which the -/// permission data is displayed -/// - [isFile] is the flag to define whether the resource is a file or not -/// - [ownerWebId] - is the web ID of the owner of the file. -/// - [granterWebId] - is the web ID of the granter of access to the file. This is usually the web ID of the user. -/// - [permDataMap] is the map of permission data for the [permDataResource] -/// - [parentWidget] is the widget to return to after an action Eg: deletion of a -/// permission -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// -Widget buildPermDataTable({ - required BuildContext context, - required String permDataResource, - required bool isFile, - required Map permDataMap, - required String ownerWebId, - required String granterWebId, - required Widget parentWidget, - required Function updatePermissionsFunction, - bool isExternalRes = false, -}) { - DataColumn buildDataColumn(String title, String tooltip) { - return DataColumn( - label: Expanded( - child: Center( - child: Text( - title, - ), - ), - ), - tooltip: tooltip, - ); - } - - // Make wide permission table horizontally scrollable - // Shows when content exceeds display width - return DataTable( - columns: [ - buildDataColumn( - 'Receiver', - 'WebID of the permission recipient', - ), - buildDataColumn('Receiver type', 'Type of the receiver'), - buildDataColumn('Permissions', 'List of permissions given'), - buildDataColumn('Actions', 'Delete permission'), - ], - // index is the webId of each individual with access to the file - rows: permDataMap.keys.map((index) { - return DataRow( - cells: [ - DataCell( - Container( - padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), - //width: cWidth, - child: Column( - children: [ - SelectableText( - (index.replaceAll('.ttl', '')) as String, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ), - ), - DataCell( - Text( - getRecipientType( - permDataMap[index][agentStr] as String, - index as String, - ).description, - ), - ), - DataCell( - Text( - (permDataMap[index][permStr] as List).join(', '), - ), - ), - // If recipient != owner, then show the delete permission button - if (ownerWebId != index) ...[ - DataCell( - // Revoke permissions icon button - RevokePermissionButton( - resourceName: permDataResource, - permDataMap: permDataMap, - receiverWebId: index, - ownerWebId: ownerWebId, - granterWebId: granterWebId, - isFile: isFile, - isExternalRes: isExternalRes, - updatePermissionsFunction: updatePermissionsFunction, - ), - ), - ] else ...[ - const DataCell( - Text(''), - ), - ], - ], - ); - }).toList(), - ); -} From 9d348d9f4d0999bccb2a6855f92bb46d101ab0d7 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 18:07:31 +1100 Subject: [PATCH 09/29] fix import order --- lib/src/solid/permission_table.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/solid/permission_table.dart b/lib/src/solid/permission_table.dart index bc298e66..2c8dfd96 100644 --- a/lib/src/solid/permission_table.dart +++ b/lib/src/solid/permission_table.dart @@ -33,8 +33,8 @@ library; import 'package:flutter/material.dart'; import 'package:solidpod/src/solid/constants/common.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/constants/ui.dart'; +import 'package:solidpod/src/solid/constants/web_acl.dart'; import 'package:solidpod/src/solid/revoke_permission_button.dart'; /// A [StatefulWidget] for listing the permissions of a resource. From 87d4ac683af38a8e77d19bb248d1da462861e969 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 19:37:36 +1100 Subject: [PATCH 10/29] isolate form related content to Form() widget --- lib/src/solid/grant_permission_ui.dart | 87 +++++++++++++------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 2bb97dac..04f73a53 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -535,13 +535,13 @@ class GrantPermissionUiState extends State child: SingleChildScrollView( controller: pageScrollController, scrollDirection: Axis.vertical, - child: Column( - children: [ - smallGapV, - Form( - key: formKey, - child: Padding( - padding: const EdgeInsets.all(10.0), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + smallGapV, + Form( + key: formKey, child: Column( children: [ // Welcome heading @@ -587,48 +587,47 @@ class GrantPermissionUiState extends State }, onUpdate: _updateCheckbox, ), - // Grant Permission Button - updates owner's ACL for - // resource, updates owner, granter, recipient logs, - // and calls updatePermissions() to refresh - // permission table. - GrantPermissionButton( - formKey: formKey, - fileNameController: fileNameController, - resourceName: widget.resourceName, - isFile: widget.isFile, - isExternalRes: widget.isExternalRes, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - selectedRecipientType: selectedRecipientType, - selectedPermList: selectedPermList, - finalWebIdList: finalWebIdList, - groupName: - selectedRecipientType == RecipientType.group - ? groupNameController.text.trim() - : null, - updatePermissionsFunction: _updatePermissions, - onPermissionGranted: widget.onPermissionGranted, - ), - largeGapV, - getHeading('Granted file access permissions'), - // Permissions table - PermissionTable( - resourceName: permDataFile, - permDataMap: permDataMap, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - updatePermissionsFunction: _updatePermissions, - parentWidget: widget.child, - isFile: getIsFile(), - isExternalRes: widget.isExternalRes, - ), ], ), ], ), ), - ), - ], + // Grant Permission Button - updates owner's ACL for + // resource, updates owner, granter, recipient logs, + // and calls updatePermissions() to refresh + // permission table. + GrantPermissionButton( + formKey: formKey, + fileNameController: fileNameController, + resourceName: widget.resourceName, + isFile: widget.isFile, + isExternalRes: widget.isExternalRes, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + selectedRecipientType: selectedRecipientType, + selectedPermList: selectedPermList, + finalWebIdList: finalWebIdList, + groupName: selectedRecipientType == RecipientType.group + ? groupNameController.text.trim() + : null, + updatePermissionsFunction: _updatePermissions, + onPermissionGranted: widget.onPermissionGranted, + ), + largeGapV, + getHeading('Granted file access permissions'), + // Permissions table + PermissionTable( + resourceName: permDataFile, + permDataMap: permDataMap, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + updatePermissionsFunction: _updatePermissions, + parentWidget: widget.child, + isFile: getIsFile(), + isExternalRes: widget.isExternalRes, + ), + ], + ), ), ), ), From 82bff5599ab9f54a4b41571f7a7f2c9492d40b1e Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sat, 17 Jan 2026 20:09:58 +1100 Subject: [PATCH 11/29] take heading and resource selection out of form widget --- lib/src/solid/grant_permission_ui.dart | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 04f73a53..fa4abf53 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -540,30 +540,31 @@ class GrantPermissionUiState extends State child: Column( children: [ smallGapV, + // Sharing heading + buildHeading( + getWelcomeStr(widget.resourceName), + 22, + Colors.blueGrey, + ), + smallGapV, + // Choose resource to share if not yet selected + if (widget.resourceName == null) ...[ + getResourceForm( + formController: fileNameController, + isFile: isFile, + onResourceTypeChange: (bool v) => + setState(() => isFile = v), + ), + smallGapV, + retrievePermissionButton, + ], Form( key: formKey, child: Column( children: [ - // Welcome heading - buildHeading( - getWelcomeStr(widget.resourceName), - 22, - Colors.blueGrey, - ), - smallGapV, Column( mainAxisSize: MainAxisSize.min, children: [ - if (widget.resourceName == null) ...[ - getResourceForm( - formController: fileNameController, - isFile: isFile, - onResourceTypeChange: (bool v) => - setState(() => isFile = v), - ), - smallGapV, - retrievePermissionButton, - ], largeGapV, getHeading( 'Select the recipient/s of file access permissions', From eb75b27e5fcb92c0e2edd36682d93a8fb6429cc3 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sun, 18 Jan 2026 13:55:40 +1100 Subject: [PATCH 12/29] add layout constants for new grant permission form dialog --- lib/src/solid/constants/ui.dart | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/src/solid/constants/ui.dart b/lib/src/solid/constants/ui.dart index a052bece..220bab38 100644 --- a/lib/src/solid/constants/ui.dart +++ b/lib/src/solid/constants/ui.dart @@ -238,3 +238,31 @@ class WebIdLayout { static const listPadding = EdgeInsets.fromLTRB(0, 5, 0, 5); } + +/// Layout constants used for Grant Permission Form Dialog + +class GrantPermFormLayout { + /// Vertical gap between paragraphs + + static const paraVertGap = SizedBox(height: 10); + + /// Standard padding for dialog content. + + static const contentPadding = EdgeInsets.symmetric(horizontal: 50); + + /// Standard width for security dialogs. + + static const dialogWidth = 480.0; + + /// Height of dropdown suggestion box. + + static const dropdownHeight = 120.0; + + /// Elevation of dropdown suggestion cards. + + static double dropdownElevation = 5; + + /// Padding of dropdown suggestion list. + + static const listPadding = EdgeInsets.fromLTRB(0, 5, 0, 5); +} From 719ddd2c58d9a2796c5886e7736102dccd4a0424 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sun, 18 Jan 2026 14:23:24 +1100 Subject: [PATCH 13/29] moved grant permission widgets to form dialog --- lib/src/solid/grant_permission_button.dart | 250 ---------- lib/src/solid/grant_permission_helper.dart | 15 +- lib/src/solid/grant_permission_ui.dart | 157 +----- lib/src/solid/share_resource_button.dart | 549 +++++++++++++++++++++ 4 files changed, 564 insertions(+), 407 deletions(-) delete mode 100644 lib/src/solid/grant_permission_button.dart create mode 100644 lib/src/solid/share_resource_button.dart diff --git a/lib/src/solid/grant_permission_button.dart b/lib/src/solid/grant_permission_button.dart deleted file mode 100644 index 5997cc76..00000000 --- a/lib/src/solid/grant_permission_button.dart +++ /dev/null @@ -1,250 +0,0 @@ -/// A button for granting 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, Dawei Chen, Ashley Tang - -library; - -import 'package:flutter/material.dart'; - -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/solid_func_call_status.dart'; -import 'package:solidpod/src/solid/utils/alert.dart'; -import 'package:solidpod/src/solid/utils/snack_bar.dart'; - -/// A [StatefulWidget] for the grant permission button. Updates owner's -/// ACL for resource, updates owner, granter, recipient logs, -/// and calls updatePermissions() to refresh permission table data. -/// -/// Parameters: -/// - [formKey] - Key of the grant permission form. -/// - [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. -/// - [selectedPermList] - is the list of permissions to be granted to the -/// [finalWebIdList]. -/// - [selectedRecipientType] - is the type of the recipient of recipients in the -/// [finalWebIdList]. -/// - [finalWebIdList] - is the list of webIds of the recipients -/// receiving access permissions to the file. -/// - [isFile] - Boolean flag describing whether the resource is a file. If false, the resource is assumed to be a directory. -/// - [groupName] - Optional name of the group permission. -/// - [updatePermissionsFunction] is the function to be called to refresh the permission table. -/// -/// - [onPermissionGranted] - Callback function called when permissions are granted successfully. - -class GrantPermissionButton extends StatefulWidget { - final GlobalKey formKey; - 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; - - final RecipientType selectedRecipientType; - final List selectedPermList; - - /// List of webIds for group permission - - final List finalWebIdList; - - /// Optional name of the group permission. - - final String? groupName; - - /// 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; - - /// Callback function called when permissions are granted successfully. - - final VoidCallback? onPermissionGranted; - - const GrantPermissionButton({ - super.key, - required this.formKey, - required this.fileNameController, - required this.selectedRecipientType, - required this.selectedPermList, - required this.finalWebIdList, - required this.updatePermissionsFunction, - this.resourceName, - required this.ownerWebId, - required this.granterWebId, - required this.isExternalRes, - required this.isFile, - this.groupName, - this.onPermissionGranted, - }); - - @override - State createState() => _GrantPermissionButtonState(); -} - -class _GrantPermissionButtonState extends State { - /// Form controller - - late final GlobalKey _formKey; - - /// Filename text controller - - late final TextEditingController _fileNameController; - - /// Selected resource - assigned once on first Grant button press - - String? dataFile; - - /// 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(); - _formKey = widget.formKey; - _fileNameController = widget.fileNameController; - } - - bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; - - /// Private function to call alert dialog in grant permission button context - Future _alert(String msg) async => alert(context, msg); - - /// Private function to show snackbar in grant permission button context - Future _showSnackBar( - String msg, - Color bgColor, { - Duration duration = const Duration(seconds: 4), - }) async => - showSnackBar(context, msg, bgColor, duration: duration); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: ElevatedButton( - onPressed: () async { - if (_formKey.currentState!.validate()) { - if (widget.selectedRecipientType.type.isNotEmpty) { - if (widget.resourceName != null) { - if (widget.selectedPermList.isNotEmpty) { - // Assign dataFile if null (first Grant press) - dataFile ??= widget.resourceName ?? _fileNameController.text; - - SolidFunctionCallStatus result; - try { - // Update ACL and permission logs to grant permission - result = await grantPermission( - fileName: dataFile!, - isFile: _getIsFile(), - permissionList: widget.selectedPermList, - recipientType: widget.selectedRecipientType, - recipientWebIdList: widget.finalWebIdList, - ownerWebId: widget.ownerWebId, - granterWebId: widget.granterWebId, - isExternalRes: widget.isExternalRes, - groupName: widget.groupName, - ); - } on Object catch (e, stackTrace) { - result = SolidFunctionCallStatus.fail; - debugPrintException(e, stackTrace); - } - - if (result == SolidFunctionCallStatus.success) { - _showSnackBar(successMsg, Colors.green); - // Update permissions table - await widget.updatePermissionsFunction( - dataFile, - isFile: _getIsFile(), - isExternalRes: widget.isExternalRes, - ); - - // Mark permissions as granted successfully for callback tracking - setState(() => permissionsGrantedSuccessfully = true); - - // Trigger the onPermissionGranted callback if provided - widget.onPermissionGranted?.call(); - } else if (result == SolidFunctionCallStatus.fail) { - // More detailed error message with troubleshooting tips - _showSnackBar(failureMsg, Colors.red); - - // Also log to console for debugging - debugPrintFailure( - dataFile!, - widget.finalWebIdList, - widget.selectedPermList, - ); - } else if (result == SolidFunctionCallStatus.notInitialised) { - _showSnackBar(podNotInitMsg, warnBgColor); - } else { - await _alert(updatePermissionMsg); - } - } else { - await _alert( - 'Please select one or more file access permissions', - ); - } - } else { - await _alert( - 'Please select one or more recipients', - ); - } - } else { - await _alert('Please select a type of recipient'); - } - } - }, - child: const Text('Grant Permission'), - ), - ); - } -} diff --git a/lib/src/solid/grant_permission_helper.dart b/lib/src/solid/grant_permission_helper.dart index 95a3c494..46f35cec 100644 --- a/lib/src/solid/grant_permission_helper.dart +++ b/lib/src/solid/grant_permission_helper.dart @@ -154,18 +154,6 @@ List getPermissionCheckBoxes( permissionCheckbox(mode, modeSwitches[mode]!, onUpdate), ]; -Widget getButton( - String text, { - required void Function() onPressed, -}) => - Padding( - padding: const EdgeInsets.all(8), - child: ElevatedButton( - onPressed: onPressed, - child: Text(text), - ), - ); - Widget getRecipientTypeButton( RecipientType recipientType, { required void Function() onPressed, @@ -238,6 +226,7 @@ Widget getRecipientText(RecipientType recipientType, String recipientDetails) => style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), Flexible( + // Show recipients if selected child: Text( '${recipientType.type}${recipientDetails.isEmpty ? "" : " ($recipientDetails)"}', style: const TextStyle( @@ -260,6 +249,8 @@ Container getButtonContainer({required List buttons}) => Container( height: 100, child: Row( children: + // TODO: move this comment to recipient buttonxs in + // ShareResourceButton() // av 20250526: // Public and Authenticated users buttons are // disabled in this function at the moment because diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index fa4abf53..c3dbd803 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -35,21 +35,19 @@ import 'package:flutter/material.dart'; import 'package:solidpod/src/solid/chk_exists_and_has_acl.dart'; import 'package:solidpod/src/solid/constants/ui.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission_button.dart'; import 'package:solidpod/src/solid/grant_permission_helper.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/solid_func_call_status.dart'; import 'package:solidpod/src/solid/utils/alert.dart'; import 'package:solidpod/src/solid/utils/authdata_manager.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; import 'package:solidpod/src/widgets/app_bar.dart'; -import 'package:solidpod/src/widgets/group_webid_input_dialog.dart'; -import 'package:solidpod/src/widgets/ind_webid_input_dialog.dart'; import 'package:solidpod/src/widgets/loading_screen.dart'; /// A [StatefulWidget] for showing and editing access permissions to a -/// file. It displays the permission table of users with access, and +/// 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. @@ -220,10 +218,6 @@ class GrantPermissionUiState extends State List recipientTypeList = []; - /// Form controller - - final formKey = GlobalKey(); - /// Filename text controller final fileNameController = TextEditingController(); @@ -383,42 +377,6 @@ class GrantPermissionUiState extends State } } - /// Update checked status of access mode selections. - - 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); - } - }); - - /// Update individual recipient webid input data - void _updateIndWebIdInput(String receiverWebId) => setState(() { - selectedRecipientType = RecipientType.individual; - selectedRecipientDetails = receiverWebId; - finalWebIdList = [receiverWebId]; - }); - - /// Update recipient group webids input data - void _updateGroupWebIdInput(String groupName, List webIdList) => - setState(() { - selectedRecipientType = RecipientType.group; - selectedRecipientDetails = - '$groupName with WebIDs ${webIdList.join(', ')}'; - finalWebIdList = webIdList; - }); - /// Private function to call alert dialog in grant permission UI context Future _alert(String msg) async => alert(context, msg); @@ -452,56 +410,6 @@ class GrantPermissionUiState extends State }, ); - final recipientTypeActions = { - RecipientType.public: () => setState(() { - selectedRecipientType = RecipientType.public; - selectedRecipientDetails = ''; - finalWebIdList = [publicAgent.value]; - }), - RecipientType.authUser: () => setState(() { - selectedRecipientType = RecipientType.authUser; - selectedRecipientDetails = ''; - finalWebIdList = [authenticatedAgent.value]; - }), - RecipientType.individual: () async => await indWebIdInputDialog( - context, - _updateIndWebIdInput, - widget.dataFilesMap, - ), - RecipientType.group: () async => await groupWebIdInputDialog( - context, - groupNameController, - groupWebIdsController, - _updateGroupWebIdInput, - ), - }; - - // 20260109 jesscmoore Added capability for granters to share - // resources, as well as resource owners - final recipientButtonContainer = getButtonContainer( - buttons: widget.isExternalRes - // Recipient type buttons for resource granter - ? [ - for (final rtype in granterRecipientTypes) - if (recipientTypeList.contains(rtype)) - getRecipientTypeButton( - rtype, - onPressed: recipientTypeActions[rtype]!, - padding: getPadding(rtype), - ), - ] - // Recipient type buttons for resource owner - : [ - for (final rtype in ownerRecipientTypes) - if (recipientTypeList.contains(rtype)) - getRecipientTypeButton( - rtype, - onPressed: recipientTypeActions[rtype]!, - padding: getPadding(rtype), - ), - ], - ); - bool getIsFile() => widget.resourceName != null ? widget.isFile : isFile; // Use customAppBar if provided @@ -558,62 +466,21 @@ class GrantPermissionUiState extends State smallGapV, retrievePermissionButton, ], - Form( - key: formKey, - child: Column( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - largeGapV, - getHeading( - 'Select the recipient/s of file access permissions', - ), - getRecipientText( - selectedRecipientType, - selectedRecipientDetails, - ), - recipientButtonContainer, - smallGapV, - getHeading( - 'Select the list of file access permissions', - ), - ...getPermissionCheckBoxes( - accessModeList, - modeSwitches: { - AccessMode.read: readChecked, - AccessMode.write: writeChecked, - AccessMode.control: controlChecked, - AccessMode.append: appendChecked, - }, - onUpdate: _updateCheckbox, - ), - ], - ), - ], - ), - ), - // Grant Permission Button - updates owner's ACL for - // resource, updates owner, granter, recipient logs, - // and calls updatePermissions() to refresh - // permission table. - GrantPermissionButton( - formKey: formKey, - fileNameController: fileNameController, + // Share resource button + ShareResourceButton( resourceName: widget.resourceName, - isFile: widget.isFile, - isExternalRes: widget.isExternalRes, + fileNameController: fileNameController, + accessModeList: widget.accessModeList, + recipientTypeList: widget.recipientTypeList, + updatePermissionsFunction: _updatePermissions, ownerWebId: _ownerWebId, granterWebId: _granterWebId, - selectedRecipientType: selectedRecipientType, - selectedPermList: selectedPermList, - finalWebIdList: finalWebIdList, - groupName: selectedRecipientType == RecipientType.group - ? groupNameController.text.trim() - : null, - updatePermissionsFunction: _updatePermissions, + isExternalRes: widget.isExternalRes, + isFile: widget.isFile, + dataFilesMap: widget.dataFilesMap, onPermissionGranted: widget.onPermissionGranted, ), + largeGapV, getHeading('Granted file access permissions'), // Permissions table diff --git a/lib/src/solid/share_resource_button.dart b/lib/src/solid/share_resource_button.dart new file mode 100644 index 00000000..b8db04fb --- /dev/null +++ b/lib/src/solid/share_resource_button.dart @@ -0,0 +1,549 @@ +/// 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:markdown_tooltip/markdown_tooltip.dart'; + +import 'package:solidpod/src/solid/constants/ui.dart'; +import 'package:solidpod/src/solid/constants/web_acl.dart'; +import 'package:solidpod/src/solid/grant_permission.dart'; +// import 'package:solidpod/src/solid/grant_permission_button.dart'; +import 'package:solidpod/src/solid/grant_permission_helper.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_dialog.dart'; +import 'package:solidpod/src/widgets/ind_webid_input_dialog.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: +/// - [formKey] - Key of the grant permission form. +/// - [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. +/// - [selectedPermList] - is the list of permissions to be granted to the +/// [finalWebIdList]. +/// - [selectedRecipientType] - is the type of the recipient of recipients in the +/// [finalWebIdList]. +/// - [finalWebIdList] - is the list of webIds of the recipients +/// receiving access permissions to the file. +/// - [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; + + // final RecipientType selectedRecipientType; + // final List selectedPermList; + + // /// List of webIds for permission + + // final List finalWebIdList; + + /// 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.selectedRecipientType, + // required this.selectedPermList, + // required this.finalWebIdList, + 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 once on first Grant button press + + String? dataFile; + + /// A flag to identify if the resource is a file or not + + bool isFile = true; + + /// Selected recipient + + RecipientType selectedRecipientType = RecipientType.none; + + /// Selected recipient details + + String selectedRecipientDetails = ''; + + /// List of webIds for group permission + + List finalWebIdList = []; + + /// Selected list of permissions + + List selectedPermList = []; + + /// Group name text controller + + final groupNameController = TextEditingController(); + + /// Group of webIds text controller + + final groupWebIdsController = TextEditingController(); + + /// 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 = []; + + /// Define recipient type list + + List recipientTypeList = []; + + // TODO: consider initialising form variables + // so each form load shows with empty variables + @override + void initState() { + super.initState(); + + _fileNameController = widget.fileNameController; + _ownerWebId = widget.ownerWebId; + _granterWebId = widget.granterWebId; + + // Load access mode list to be displayed + for (final accessModeStr in widget.accessModeList) { + accessModeList.add(getAccessMode(accessModeStr)); + } + + // Load recipient type list to be displayed + for (final recTypeStr in widget.recipientTypeList) { + recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); + } + } + + @override + void dispose() { + _fileNameController.dispose(); // Dispose filename editing controller + groupNameController.dispose(); // Dispose group name editing controller + groupWebIdsController.dispose(); // Dispose group webids editing controller + super.dispose(); + } + + bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; + + /// 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); + + /// Sharing (grant permission) form dialog function + /// + Future _grantPermissionFormDialog(BuildContext context) async { + debugPrint('recipientTypeList: ${recipientTypeList.toString()}'); + + // 20260118 jesscmoore: if useful could update GrantPermissionUi() + // variables by adding return variable result to showDialog(), and + // then checking if result != null then call setState() to update + // GrantPermissionUi(). updatePermissions() after grantPermission() + // effectively does this. + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + insetPadding: GrantPermFormLayout.contentPadding, + title: const Text('Share resource'), + content: StatefulBuilder( + builder: (BuildContext stfContext, StateSetter stfSetState) { + /// Update selected webid list with individual recipient webid + /// [receiverWebId]. + void updateIndWebIdInput( + String receiverWebId, + ) => + stfSetState(() { + selectedRecipientType = RecipientType.individual; + 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, + ) => + stfSetState(() { + selectedRecipientType = RecipientType.group; + selectedRecipientDetails = + '$groupName with WebIDs ${webIdList.join(', ')}'; + finalWebIdList = webIdList; + }); + + /// Define button click actions for each recipient type button + /// and store in map variable with button click action function + /// for each recipient type key + final Map recipientTypeActions = { + RecipientType.public: () => stfSetState(() { + selectedRecipientType = RecipientType.public; + selectedRecipientDetails = ''; + finalWebIdList = [publicAgent.value]; + }), + RecipientType.authUser: () => stfSetState(() { + selectedRecipientType = RecipientType.authUser; + selectedRecipientDetails = ''; + finalWebIdList = [authenticatedAgent.value]; + }), + RecipientType.individual: () async => await indWebIdInputDialog( + context, + updateIndWebIdInput, + widget.dataFilesMap, + ), + RecipientType.group: () async => await groupWebIdInputDialog( + context, + groupNameController, + groupWebIdsController, + updateGroupWebIdInput, + ), + }; + + /// Update checked status of access mode boxes to show + /// selected access modes. + void updateCheckbox(bool newValue, AccessMode accessMode) => + stfSetState(() { + 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); + } + }); + + return 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: [ + getHeading( + 'Select the recipient/s of file access permissions', + ), + // FIXME: update widget name to be more meaningful + getRecipientText( + selectedRecipientType, + selectedRecipientDetails, + ), + + // 20260109 jesscmoore Added capability for granters to share + // resources, as well as resource owners + getButtonContainer( + buttons: widget.isExternalRes + // Recipient type buttons for resource granter + ? [ + for (final rtype in granterRecipientTypes) + if (recipientTypeList.contains(rtype)) + getRecipientTypeButton( + rtype, + onPressed: recipientTypeActions[rtype]!, + padding: getPadding(rtype), + ), + ] + // Recipient type buttons for resource owner + : [ + for (final rtype in ownerRecipientTypes) + if (recipientTypeList.contains(rtype)) + Expanded( + child: Container( + padding: getPadding(rtype), + height: 50, + child: MarkdownTooltip( + message: recipientToolTips[rtype]!, + child: ElevatedButton( + onPressed: + recipientTypeActions[rtype]!, + child: Text(rtype.description), + ), + ), + ), + ), + ], + ), + smallGapV, + getHeading( + 'Select the list of 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 (widget.resourceName != null) { + if (selectedPermList.isNotEmpty) { + // Assign dataFile if null (first Grant press) + dataFile ??= + widget.resourceName ?? _fileNameController.text; + + SolidFunctionCallStatus result; + try { + // Update ACL and permission logs to grant permission + result = await grantPermission( + fileName: dataFile!, + isFile: _getIsFile(), + permissionList: selectedPermList, + recipientType: selectedRecipientType, + recipientWebIdList: finalWebIdList, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + isExternalRes: widget.isExternalRes, + groupName: + selectedRecipientType == RecipientType.group + ? groupNameController.text.trim() + : null, + ); + + // 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, Colors.green); + // Update permissions table + await widget.updatePermissionsFunction( + dataFile, + isFile: _getIsFile(), + isExternalRes: widget.isExternalRes, + ); + + // Mark permissions as granted successfully for callback tracking + setState(() => permissionsGrantedSuccessfully = true); + + // Trigger the onPermissionGranted callback if provided + widget.onPermissionGranted?.call(); + } else if (result == SolidFunctionCallStatus.fail) { + // More detailed error message with troubleshooting tips + _showSnackBar(failureMsg, Colors.red); + + // Also log to console for debugging + debugPrintFailure( + dataFile!, + finalWebIdList, + selectedPermList, + ); + } else if (result == + SolidFunctionCallStatus.notInitialised) { + _showSnackBar(podNotInitMsg, warnBgColor); + } else { + await _alert(updatePermissionMsg); + } + } else { + await _alert( + 'Please select one or more file access permissions', + ); + } + } else { + await _alert( + 'Please select one or more recipients', + ); + } + } 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'), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: ElevatedButton.icon( + icon: const Icon( + Icons.share, + ), + onPressed: () async { + await _grantPermissionFormDialog(context); + }, + label: const Text('Share Resource'), + ), + ); + } +} From d2e9a93b2d0054dfd50986c0672c99f6b84ab96a Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Sun, 18 Jan 2026 21:48:42 +1100 Subject: [PATCH 14/29] fix initialising of form --- lib/src/solid/grant_permission_form.dart | 488 +++++++++++++++++++++++ lib/src/solid/grant_permission_ui.dart | 64 +-- lib/src/solid/share_resource_button.dart | 409 ++----------------- 3 files changed, 544 insertions(+), 417 deletions(-) create mode 100644 lib/src/solid/grant_permission_form.dart diff --git a/lib/src/solid/grant_permission_form.dart b/lib/src/solid/grant_permission_form.dart new file mode 100644 index 00000000..435ba612 --- /dev/null +++ b/lib/src/solid/grant_permission_form.dart @@ -0,0 +1,488 @@ +/// 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:markdown_tooltip/markdown_tooltip.dart'; + +import 'package:solidpod/src/solid/constants/ui.dart'; +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/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_dialog.dart'; +import 'package:solidpod/src/widgets/ind_webid_input_dialog.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 { + /// Owner WebId + + late final String _ownerWebId; + + /// Granter WebId + + late final String _granterWebId; + + /// Selected resource being shared + + late final String _resourceName; + + /// Selected recipient + + RecipientType selectedRecipientType = RecipientType.none; + + /// Selected recipient details + + String selectedRecipientDetails = ''; + + /// List of webIds for group permission + + List finalWebIdList = []; + + /// Selected list of permissions + + List selectedPermList = []; + + /// Group name text controller + + final groupNameController = TextEditingController(); + + /// Group of webIds text controller + + final groupWebIdsController = TextEditingController(); + + /// 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 = []; + + /// Define recipient type list + + List recipientTypeList = []; + + @override + void initState() { + super.initState(); + + // _fileNameController = widget.fileNameController; + _resourceName = widget.resourceName; + _ownerWebId = widget.ownerWebId; + _granterWebId = widget.granterWebId; + + // Load access mode list to be displayed + for (final accessModeStr in widget.accessModeList) { + accessModeList.add(getAccessMode(accessModeStr)); + } + + // Load recipient type list to be displayed + for (final recTypeStr in widget.recipientTypeList) { + recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); + } + } + + @override + void dispose() { + groupNameController.dispose(); // Dispose group name editing controller + groupWebIdsController.dispose(); // Dispose group webids editing controller + 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(() { + selectedRecipientType = RecipientType.individual; + 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(() { + selectedRecipientType = RecipientType.group; + selectedRecipientDetails = + '$groupName with WebIDs ${webIdList.join(', ')}'; + finalWebIdList = webIdList; + }); + + /// 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); + } + }); + + @override + Widget build(BuildContext context) { + /// Define button click actions for each recipient type button + /// and store in map variable with button click action function + /// for each recipient type key + final Map recipientTypeActions = { + RecipientType.public: () => setState(() { + selectedRecipientType = RecipientType.public; + selectedRecipientDetails = ''; + finalWebIdList = [publicAgent.value]; + }), + RecipientType.authUser: () => setState(() { + selectedRecipientType = RecipientType.authUser; + selectedRecipientDetails = ''; + finalWebIdList = [authenticatedAgent.value]; + }), + RecipientType.individual: () async => await indWebIdInputDialog( + context, + updateIndWebIdInput, + widget.dataFilesMap, + ), + RecipientType.group: () async => await groupWebIdInputDialog( + context, + groupNameController, + groupWebIdsController, + updateGroupWebIdInput, + ), + }; + + return AlertDialog( + insetPadding: GrantPermFormLayout.contentPadding, + title: const Text('Share resource'), + // content: StatefulBuilder( + // builder: (BuildContext stfContext, StateSetter setState) { + // return SizedBox( + content: 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: [ + getHeading( + 'Select the recipient/s of file access permissions', + ), + // FIXME: update widget name to be more meaningful + // show selected recipients + getRecipientText( + selectedRecipientType, + selectedRecipientDetails, + ), + + // 20260109 jesscmoore Added capability for granters to share + // resources, as well as resource owners + // FIXME: create RecipientTypeButton widget class + getButtonContainer( + buttons: widget.isExternalRes + // Recipient type buttons for resource granter + // TODO: check whether 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. + ? [ + for (final rtype in granterRecipientTypes) + if (recipientTypeList.contains(rtype)) + getRecipientTypeButton( + rtype, + onPressed: recipientTypeActions[rtype]!, + padding: getPadding(rtype), + ), + ] + // Recipient type buttons for resource owner + : [ + for (final rtype in ownerRecipientTypes) + if (recipientTypeList.contains(rtype)) + Expanded( + child: Container( + padding: getPadding(rtype), + height: 50, + child: MarkdownTooltip( + message: recipientToolTips[rtype]!, + child: ElevatedButton( + onPressed: recipientTypeActions[rtype]!, + child: Text(rtype.description), + ), + ), + ), + ), + ], + ), + smallGapV, + getHeading( + 'Select the list of 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: _resourceName, + isFile: widget.isFile, + permissionList: selectedPermList, + recipientType: selectedRecipientType, + recipientWebIdList: finalWebIdList, + ownerWebId: _ownerWebId, + granterWebId: _granterWebId, + isExternalRes: widget.isExternalRes, + groupName: selectedRecipientType == RecipientType.group + ? groupNameController.text.trim() + : null, + ); + + // 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, Colors.green); + // Update permissions table + await widget.updatePermissionsFunction( + _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, Colors.red); + + // Also log to console for debugging + debugPrintFailure( + _resourceName, + finalWebIdList, + selectedPermList, + ); + } else if (result == SolidFunctionCallStatus.notInitialised) { + _showSnackBar(podNotInitMsg, warnBgColor); + } 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_ui.dart b/lib/src/solid/grant_permission_ui.dart index c3dbd803..f349ff89 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -182,30 +182,6 @@ class GrantPermissionUi extends StatefulWidget { class GrantPermissionUiState extends State with SingleTickerProviderStateMixin { - /// 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; - - /// WebId textfield enable/disable flag - - bool webIdTextFieldEnabled = true; - /// Flag to check whether page is initialised. bool pageInitialied = false; @@ -222,14 +198,6 @@ class GrantPermissionUiState extends State final fileNameController = TextEditingController(); - /// Group name text controller - - final groupNameController = TextEditingController(); - - /// Group of webIds text controller - - final groupWebIdsController = TextEditingController(); - /// Permission data map of a file Map permDataMap = {}; @@ -246,22 +214,6 @@ class GrantPermissionUiState extends State String permDataFile = ''; - /// Selected recipient - - RecipientType selectedRecipientType = RecipientType.none; - - /// Selected recipient details - - String selectedRecipientDetails = ''; - - /// List of webIds for group permission - - List finalWebIdList = []; - - /// Selected list of permissions - - List selectedPermList = []; - /// Flag to track if permissions were granted successfully. bool permissionsGrantedSuccessfully = false; @@ -273,6 +225,9 @@ class GrantPermissionUiState extends State /// A flag to identify if the resource is a file or not bool isFile = true; + // TODO: tidy loadPodData() removing owner and granter webid fetching + // to initState + /// Runs multiple asynchronous functions to get the data from /// POD server if necessary. @@ -289,12 +244,19 @@ class GrantPermissionUiState extends State switch (response) { case SolidFunctionCallStatus.aclFound: + + // TODO: move readPermission(), getting owner and granter into + // new getPermissionDetails() to get the permission map, owner + // and granter of a resource. final Map result = await readPermission( fileName: resName, isFile: isFile, isExternalRes: widget.isExternalRes, ); + // TODO: write get_authoriser.dart with functions to get + // ownerWebId and granterWebId + // Fetch owner's webID // ownerWebId == userWebId if not externally owned resource @@ -380,6 +342,8 @@ class GrantPermissionUiState extends State /// Private function to call alert dialog in grant permission UI context Future _alert(String msg) async => alert(context, msg); + // TODO: rename futureObjList to dataMap + /// Build the main widget Widget _buildPermPage(BuildContext context, [List? futureObjList]) { /// Controller for vertical page scrolling @@ -390,6 +354,7 @@ class GrantPermissionUiState extends State permDataMap = futureObjList.first as Map; _ownerWebId = futureObjList[1] as String; _granterWebId = futureObjList.last as String; + // FIXME: ensure permDataFile updates within _updatePermissions() to be correct when resource selected within GrantPermissionUi() permDataFile = widget.resourceName!; pageInitialied = true; } @@ -455,7 +420,8 @@ class GrantPermissionUiState extends State Colors.blueGrey, ), smallGapV, - // Choose resource to share if not yet selected + // Choose resource and run _updatePermissions if + // resourceName not provided if (widget.resourceName == null) ...[ getResourceForm( formController: fileNameController, diff --git a/lib/src/solid/share_resource_button.dart b/lib/src/solid/share_resource_button.dart index b8db04fb..9c3654c7 100644 --- a/lib/src/solid/share_resource_button.dart +++ b/lib/src/solid/share_resource_button.dart @@ -32,26 +32,14 @@ library; import 'package:flutter/material.dart'; -import 'package:markdown_tooltip/markdown_tooltip.dart'; - -import 'package:solidpod/src/solid/constants/ui.dart'; -import 'package:solidpod/src/solid/constants/web_acl.dart'; -import 'package:solidpod/src/solid/grant_permission.dart'; -// import 'package:solidpod/src/solid/grant_permission_button.dart'; -import 'package:solidpod/src/solid/grant_permission_helper.dart'; -import 'package:solidpod/src/solid/solid_func_call_status.dart'; +import 'package:solidpod/src/solid/grant_permission_form.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_dialog.dart'; -import 'package:solidpod/src/widgets/ind_webid_input_dialog.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: -/// - [formKey] - Key of the grant permission form. /// - [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. @@ -61,12 +49,6 @@ import 'package:solidpod/src/widgets/ind_webid_input_dialog.dart'; /// - [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. -/// - [selectedPermList] - is the list of permissions to be granted to the -/// [finalWebIdList]. -/// - [selectedRecipientType] - is the type of the recipient of recipients in the -/// [finalWebIdList]. -/// - [finalWebIdList] - is the list of webIds of the recipients -/// receiving access permissions to the file. /// - [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. /// @@ -103,13 +85,6 @@ class ShareResourceButton extends StatefulWidget { final List recipientTypeList; - // final RecipientType selectedRecipientType; - // final List selectedPermList; - - // /// List of webIds for permission - - // final List finalWebIdList; - /// 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 @@ -135,9 +110,6 @@ class ShareResourceButton extends StatefulWidget { required this.granterWebId, this.accessModeList = const ['read', 'write', 'append', 'control'], this.recipientTypeList = const ['public', 'indi', 'auth', 'group'], - // required this.selectedRecipientType, - // required this.selectedPermList, - // required this.finalWebIdList, required this.isExternalRes, required this.isFile, this.dataFilesMap = const {}, @@ -161,72 +133,18 @@ class _ShareResourceButtonState extends State { late final String _granterWebId; - /// Selected resource - assigned once on first Grant button press + /// Selected resource - assigned on Share Resource button press - String? dataFile; + String _resourceName = ''; /// A flag to identify if the resource is a file or not bool isFile = true; - /// Selected recipient - - RecipientType selectedRecipientType = RecipientType.none; - - /// Selected recipient details - - String selectedRecipientDetails = ''; - - /// List of webIds for group permission - - List finalWebIdList = []; - - /// Selected list of permissions - - List selectedPermList = []; - - /// Group name text controller - - final groupNameController = TextEditingController(); - - /// Group of webIds text controller - - final groupWebIdsController = TextEditingController(); - /// 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 = []; - - /// Define recipient type list - - List recipientTypeList = []; - - // TODO: consider initialising form variables - // so each form load shows with empty variables @override void initState() { super.initState(); @@ -234,302 +152,28 @@ class _ShareResourceButtonState extends State { _fileNameController = widget.fileNameController; _ownerWebId = widget.ownerWebId; _granterWebId = widget.granterWebId; - - // Load access mode list to be displayed - for (final accessModeStr in widget.accessModeList) { - accessModeList.add(getAccessMode(accessModeStr)); - } - - // Load recipient type list to be displayed - for (final recTypeStr in widget.recipientTypeList) { - recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); - } } @override void dispose() { _fileNameController.dispose(); // Dispose filename editing controller - groupNameController.dispose(); // Dispose group name editing controller - groupWebIdsController.dispose(); // Dispose group webids editing controller super.dispose(); } - bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; + /// Mark permissions as granted successfully for callback tracking + Future _updatePermissionGrantedStatus() async { + debugPrint('_updatePermissionGrantedStatus(): ...'); + + 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); - /// 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); - - /// Sharing (grant permission) form dialog function - /// - Future _grantPermissionFormDialog(BuildContext context) async { - debugPrint('recipientTypeList: ${recipientTypeList.toString()}'); - - // 20260118 jesscmoore: if useful could update GrantPermissionUi() - // variables by adding return variable result to showDialog(), and - // then checking if result != null then call setState() to update - // GrantPermissionUi(). updatePermissions() after grantPermission() - // effectively does this. - showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - insetPadding: GrantPermFormLayout.contentPadding, - title: const Text('Share resource'), - content: StatefulBuilder( - builder: (BuildContext stfContext, StateSetter stfSetState) { - /// Update selected webid list with individual recipient webid - /// [receiverWebId]. - void updateIndWebIdInput( - String receiverWebId, - ) => - stfSetState(() { - selectedRecipientType = RecipientType.individual; - 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, - ) => - stfSetState(() { - selectedRecipientType = RecipientType.group; - selectedRecipientDetails = - '$groupName with WebIDs ${webIdList.join(', ')}'; - finalWebIdList = webIdList; - }); - - /// Define button click actions for each recipient type button - /// and store in map variable with button click action function - /// for each recipient type key - final Map recipientTypeActions = { - RecipientType.public: () => stfSetState(() { - selectedRecipientType = RecipientType.public; - selectedRecipientDetails = ''; - finalWebIdList = [publicAgent.value]; - }), - RecipientType.authUser: () => stfSetState(() { - selectedRecipientType = RecipientType.authUser; - selectedRecipientDetails = ''; - finalWebIdList = [authenticatedAgent.value]; - }), - RecipientType.individual: () async => await indWebIdInputDialog( - context, - updateIndWebIdInput, - widget.dataFilesMap, - ), - RecipientType.group: () async => await groupWebIdInputDialog( - context, - groupNameController, - groupWebIdsController, - updateGroupWebIdInput, - ), - }; - - /// Update checked status of access mode boxes to show - /// selected access modes. - void updateCheckbox(bool newValue, AccessMode accessMode) => - stfSetState(() { - 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); - } - }); - - return 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: [ - getHeading( - 'Select the recipient/s of file access permissions', - ), - // FIXME: update widget name to be more meaningful - getRecipientText( - selectedRecipientType, - selectedRecipientDetails, - ), - - // 20260109 jesscmoore Added capability for granters to share - // resources, as well as resource owners - getButtonContainer( - buttons: widget.isExternalRes - // Recipient type buttons for resource granter - ? [ - for (final rtype in granterRecipientTypes) - if (recipientTypeList.contains(rtype)) - getRecipientTypeButton( - rtype, - onPressed: recipientTypeActions[rtype]!, - padding: getPadding(rtype), - ), - ] - // Recipient type buttons for resource owner - : [ - for (final rtype in ownerRecipientTypes) - if (recipientTypeList.contains(rtype)) - Expanded( - child: Container( - padding: getPadding(rtype), - height: 50, - child: MarkdownTooltip( - message: recipientToolTips[rtype]!, - child: ElevatedButton( - onPressed: - recipientTypeActions[rtype]!, - child: Text(rtype.description), - ), - ), - ), - ), - ], - ), - smallGapV, - getHeading( - 'Select the list of 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 (widget.resourceName != null) { - if (selectedPermList.isNotEmpty) { - // Assign dataFile if null (first Grant press) - dataFile ??= - widget.resourceName ?? _fileNameController.text; - - SolidFunctionCallStatus result; - try { - // Update ACL and permission logs to grant permission - result = await grantPermission( - fileName: dataFile!, - isFile: _getIsFile(), - permissionList: selectedPermList, - recipientType: selectedRecipientType, - recipientWebIdList: finalWebIdList, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, - isExternalRes: widget.isExternalRes, - groupName: - selectedRecipientType == RecipientType.group - ? groupNameController.text.trim() - : null, - ); - - // 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, Colors.green); - // Update permissions table - await widget.updatePermissionsFunction( - dataFile, - isFile: _getIsFile(), - isExternalRes: widget.isExternalRes, - ); - - // Mark permissions as granted successfully for callback tracking - setState(() => permissionsGrantedSuccessfully = true); - - // Trigger the onPermissionGranted callback if provided - widget.onPermissionGranted?.call(); - } else if (result == SolidFunctionCallStatus.fail) { - // More detailed error message with troubleshooting tips - _showSnackBar(failureMsg, Colors.red); - - // Also log to console for debugging - debugPrintFailure( - dataFile!, - finalWebIdList, - selectedPermList, - ); - } else if (result == - SolidFunctionCallStatus.notInitialised) { - _showSnackBar(podNotInitMsg, warnBgColor); - } else { - await _alert(updatePermissionMsg); - } - } else { - await _alert( - 'Please select one or more file access permissions', - ); - } - } else { - await _alert( - 'Please select one or more recipients', - ); - } - } 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'), - ), - ], - ); - }, - ); - } + // Resource is a file if resource selected in GrantPermissionUi() + bool _getIsFile() => widget.resourceName != null ? widget.isFile : isFile; @override Widget build(BuildContext context) { @@ -540,7 +184,36 @@ class _ShareResourceButtonState extends State { Icons.share, ), onPressed: () async { - await _grantPermissionFormDialog(context); + // 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'), ), From ca8d658c4dcd45e2faa312a8cd6b26b9d62bf86b Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 00:13:16 +1100 Subject: [PATCH 15/29] convert select recipient button container to stateful widget for code interpretability --- lib/src/solid/grant_permission_form.dart | 137 ++++++--------- lib/src/solid/select_recipients.dart | 207 +++++++++++++++++++++++ 2 files changed, 256 insertions(+), 88 deletions(-) create mode 100644 lib/src/solid/select_recipients.dart diff --git a/lib/src/solid/grant_permission_form.dart b/lib/src/solid/grant_permission_form.dart index 435ba612..a49250bb 100644 --- a/lib/src/solid/grant_permission_form.dart +++ b/lib/src/solid/grant_permission_form.dart @@ -26,18 +26,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /// -/// Authors: Jess Moore +/// Authors: Jess Moore, Anushka Vidanage library; import 'package:flutter/material.dart'; -import 'package:markdown_tooltip/markdown_tooltip.dart'; - import 'package:solidpod/src/solid/constants/ui.dart'; 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/solid_func_call_status.dart'; import 'package:solidpod/src/solid/utils/alert.dart'; import 'package:solidpod/src/solid/utils/is_phone.dart'; @@ -198,15 +197,10 @@ class _GrantPermissionFormState extends State { List accessModeList = []; - /// Define recipient type list - - List recipientTypeList = []; - @override void initState() { super.initState(); - // _fileNameController = widget.fileNameController; _resourceName = widget.resourceName; _ownerWebId = widget.ownerWebId; _granterWebId = widget.granterWebId; @@ -215,11 +209,6 @@ class _GrantPermissionFormState extends State { for (final accessModeStr in widget.accessModeList) { accessModeList.add(getAccessMode(accessModeStr)); } - - // Load recipient type list to be displayed - for (final recTypeStr in widget.recipientTypeList) { - recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); - } } @override @@ -288,41 +277,42 @@ class _GrantPermissionFormState extends State { } }); + /// Define button click actions for each recipient type button + + /// Set recipients to public + void _setRecipientsToPublic() => setState(() { + selectedRecipientType = RecipientType.public; + selectedRecipientDetails = ''; + finalWebIdList = [publicAgent.value]; + }); + + /// Set recipients to authorised users + void _setRecipientsToAuthUsers() => setState(() { + selectedRecipientType = RecipientType.authUser; + selectedRecipientDetails = ''; + finalWebIdList = [authenticatedAgent.value]; + }); + + /// Select individual recipient + void _setRecipientsToIndividual() async => await indWebIdInputDialog( + context, + updateIndWebIdInput, + widget.dataFilesMap, + ); + + /// Select a group of recipients + void _setRecipientsToGroup() async => await groupWebIdInputDialog( + context, + groupNameController, + groupWebIdsController, + updateGroupWebIdInput, + ); + @override Widget build(BuildContext context) { - /// Define button click actions for each recipient type button - /// and store in map variable with button click action function - /// for each recipient type key - final Map recipientTypeActions = { - RecipientType.public: () => setState(() { - selectedRecipientType = RecipientType.public; - selectedRecipientDetails = ''; - finalWebIdList = [publicAgent.value]; - }), - RecipientType.authUser: () => setState(() { - selectedRecipientType = RecipientType.authUser; - selectedRecipientDetails = ''; - finalWebIdList = [authenticatedAgent.value]; - }), - RecipientType.individual: () async => await indWebIdInputDialog( - context, - updateIndWebIdInput, - widget.dataFilesMap, - ), - RecipientType.group: () async => await groupWebIdInputDialog( - context, - groupNameController, - groupWebIdsController, - updateGroupWebIdInput, - ), - }; - return AlertDialog( insetPadding: GrantPermFormLayout.contentPadding, title: const Text('Share resource'), - // content: StatefulBuilder( - // builder: (BuildContext stfContext, StateSetter setState) { - // return SizedBox( content: SizedBox( // Use full width on phones, else use a preset narrower width width: @@ -334,55 +324,26 @@ class _GrantPermissionFormState extends State { getHeading( 'Select the recipient/s of file access permissions', ), - // FIXME: update widget name to be more meaningful - // show selected recipients - getRecipientText( + // List selected recipient webids or recipient + // type (public/auth) + // TODO: convert to stless widget + showSelectedRecipients( selectedRecipientType, selectedRecipientDetails, ), - // 20260109 jesscmoore Added capability for granters to share - // resources, as well as resource owners - // FIXME: create RecipientTypeButton widget class - getButtonContainer( - buttons: widget.isExternalRes - // Recipient type buttons for resource granter - // TODO: check whether 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. - ? [ - for (final rtype in granterRecipientTypes) - if (recipientTypeList.contains(rtype)) - getRecipientTypeButton( - rtype, - onPressed: recipientTypeActions[rtype]!, - padding: getPadding(rtype), - ), - ] - // Recipient type buttons for resource owner - : [ - for (final rtype in ownerRecipientTypes) - if (recipientTypeList.contains(rtype)) - Expanded( - child: Container( - padding: getPadding(rtype), - height: 50, - child: MarkdownTooltip( - message: recipientToolTips[rtype]!, - child: ElevatedButton( - onPressed: recipientTypeActions[rtype]!, - child: Text(rtype.description), - ), - ), - ), - ), - ], + // Show Select Recipient Buttons + SelectRecipients( + isExternalRes: widget.isExternalRes, + recipientTypeList: widget.recipientTypeList, + setPublicFunction: _setRecipientsToPublic, + setAuthUsersFunction: _setRecipientsToAuthUsers, + setIndividualFunction: _setRecipientsToIndividual, + setGroupFunction: _setRecipientsToGroup, + updateIndWebIdFunction: updateIndWebIdInput, + updateGroupWebIdFunction: updateGroupWebIdInput, ), + smallGapV, getHeading( 'Select the list of file access permissions', @@ -401,7 +362,7 @@ class _GrantPermissionFormState extends State { ), ], ), - ), //; + ), actions: [ TextButton( onPressed: () async { diff --git a/lib/src/solid/select_recipients.dart b/lib/src/solid/select_recipients.dart new file mode 100644 index 00000000..13bcc23e --- /dev/null +++ b/lib/src/solid/select_recipients.dart @@ -0,0 +1,207 @@ +/// 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. +/// - [updateIndWebIdFunction] - a function to update the selected +/// individual webId. +/// - [updateGroupWebIdFunction] - a function to update the selected +/// group webId list and group name. + +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; + + /// 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; + + /// 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; + + /// A function to update the selected individual webId. + /// + final Function updateIndWebIdFunction; + + /// A function to update the selected group webId list and group + /// name. + + final Function updateGroupWebIdFunction; + + const SelectRecipients({ + super.key, + required this.isExternalRes, + required this.recipientTypeList, + required this.setPublicFunction, + required this.setAuthUsersFunction, + required this.setIndividualFunction, + required this.setGroupFunction, + required this.updateIndWebIdFunction, + required this.updateGroupWebIdFunction, + this.dataFilesMap = const {}, + }); + + @override + State createState() => _SelectRecipientsState(); +} + +class _SelectRecipientsState extends State { + /// Group name text controller + + final groupNameController = TextEditingController(); + + /// Group of webIds text controller + + final groupWebIdsController = TextEditingController(); + + /// Define recipient type list + + List recipientTypeList = []; + + @override + void initState() { + super.initState(); + + // Load recipient type list to be displayed + for (final recTypeStr in widget.recipientTypeList) { + recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); + } + } + + @override + void dispose() { + groupNameController.dispose(); // Dispose group name editing controller + groupWebIdsController.dispose(); // Dispose group webids editing controller + super.dispose(); + } + + List selectRecipientButtons({ + required List allowedRecipientTypes, + }) { + return [ + for (final rtype in allowedRecipientTypes) + if (recipientTypeList.contains(rtype)) + Expanded( + child: Container( + padding: getPadding(rtype), + height: 50, + child: MarkdownTooltip( + message: recipientToolTips[rtype]!, + child: ElevatedButton( + onPressed: () { + switch (rtype) { + case RecipientType.public: + widget.setPublicFunction(); + case RecipientType.authUser: + widget.setAuthUsersFunction(); + case RecipientType.individual: + widget.setIndividualFunction(); + case RecipientType.group: + widget.setGroupFunction(); + case RecipientType.none: + return; + } + }, + child: Text(rtype.description), + ), + ), + ), + ), + ]; + } + + @override + Widget build(BuildContext context) { + // 20260109 jesscmoore Added capability for granters to share + // resources, as well as resource owners + return getButtonContainer( + buttons: widget.isExternalRes + + // TODO jesscmoore 20260118: 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. + + // Recipient type buttons for resource granter + ? selectRecipientButtons(allowedRecipientTypes: granterRecipientTypes) + // Recipient type buttons for resource owner + : selectRecipientButtons(allowedRecipientTypes: ownerRecipientTypes), + ); + } +} From 2bd1c4d8d49e43efc88a8580993c41978123c278 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 00:49:29 +1100 Subject: [PATCH 16/29] move code to widgets and tidy --- lib/src/solid/grant_permission_form.dart | 34 ++----- lib/src/solid/grant_permission_helper.dart | 88 ----------------- lib/src/solid/select_recipients.dart | 40 ++++---- lib/src/solid/show_selected_recipients.dart | 101 ++++++++++++++++++++ 4 files changed, 134 insertions(+), 129 deletions(-) create mode 100644 lib/src/solid/show_selected_recipients.dart diff --git a/lib/src/solid/grant_permission_form.dart b/lib/src/solid/grant_permission_form.dart index a49250bb..23f4aa8e 100644 --- a/lib/src/solid/grant_permission_form.dart +++ b/lib/src/solid/grant_permission_form.dart @@ -37,6 +37,7 @@ 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'; @@ -133,18 +134,6 @@ class GrantPermissionForm extends StatefulWidget { } class _GrantPermissionFormState extends State { - /// Owner WebId - - late final String _ownerWebId; - - /// Granter WebId - - late final String _granterWebId; - - /// Selected resource being shared - - late final String _resourceName; - /// Selected recipient RecipientType selectedRecipientType = RecipientType.none; @@ -201,10 +190,6 @@ class _GrantPermissionFormState extends State { void initState() { super.initState(); - _resourceName = widget.resourceName; - _ownerWebId = widget.ownerWebId; - _granterWebId = widget.granterWebId; - // Load access mode list to be displayed for (final accessModeStr in widget.accessModeList) { accessModeList.add(getAccessMode(accessModeStr)); @@ -326,10 +311,9 @@ class _GrantPermissionFormState extends State { ), // List selected recipient webids or recipient // type (public/auth) - // TODO: convert to stless widget - showSelectedRecipients( - selectedRecipientType, - selectedRecipientDetails, + ShowSelectedRecipients( + selectedRecipientType: selectedRecipientType, + selectedRecipientDetails: selectedRecipientDetails, ), // Show Select Recipient Buttons @@ -375,13 +359,13 @@ class _GrantPermissionFormState extends State { try { // Update ACL and permission logs to grant permission result = await grantPermission( - fileName: _resourceName, + fileName: widget.resourceName, isFile: widget.isFile, permissionList: selectedPermList, recipientType: selectedRecipientType, recipientWebIdList: finalWebIdList, - ownerWebId: _ownerWebId, - granterWebId: _granterWebId, + ownerWebId: widget.ownerWebId, + granterWebId: widget.granterWebId, isExternalRes: widget.isExternalRes, groupName: selectedRecipientType == RecipientType.group ? groupNameController.text.trim() @@ -400,7 +384,7 @@ class _GrantPermissionFormState extends State { _showSnackBar(successMsg, Colors.green); // Update permissions table await widget.updatePermissionsFunction( - _resourceName, + widget.resourceName, //_resourceName, isFile: widget.isFile, isExternalRes: widget.isExternalRes, ); @@ -416,7 +400,7 @@ class _GrantPermissionFormState extends State { // Also log to console for debugging debugPrintFailure( - _resourceName, + widget.resourceName, // _resourceName, finalWebIdList, selectedPermList, ); diff --git a/lib/src/solid/grant_permission_helper.dart b/lib/src/solid/grant_permission_helper.dart index 46f35cec..259b8d5b 100644 --- a/lib/src/solid/grant_permission_helper.dart +++ b/lib/src/solid/grant_permission_helper.dart @@ -28,8 +28,6 @@ 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/utils/heading.dart'; import 'package:solidpod/src/widgets/permission_checkbox.dart'; @@ -154,27 +152,6 @@ List getPermissionCheckBoxes( permissionCheckbox(mode, modeSwitches[mode]!, onUpdate), ]; -Widget getRecipientTypeButton( - RecipientType recipientType, { - required void Function() onPressed, - EdgeInsetsGeometry? padding, -}) { - assert(recipientType != RecipientType.none); - return Expanded( - child: Container( - padding: padding, - height: 50, - child: MarkdownTooltip( - message: recipientToolTips[recipientType]!, - child: ElevatedButton( - onPressed: onPressed, - child: Text(recipientType.description), - ), - ), - ), - ); -} - Widget getResourceForm({ required TextEditingController formController, required bool isFile, @@ -215,68 +192,3 @@ Widget getResourceForm({ ], ), ); - -Widget getRecipientText(RecipientType recipientType, String recipientDetails) => - Container( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Text( - 'Recipient/s: ', - style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), - ), - Flexible( - // Show recipients if selected - child: Text( - '${recipientType.type}${recipientDetails.isEmpty ? "" : " ($recipientDetails)"}', - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - // 20251008 gjw Choose blue rather than - // orange which looks red. The red looks - // like it is an error. Blue is more - // neutral. - color: Colors.blueAccent, - ), - ), - ), - ], - ), - ); - -Container getButtonContainer({required List buttons}) => Container( - padding: const EdgeInsets.all(8.0), - height: 100, - child: Row( - children: - // TODO: move this comment to recipient buttonxs in - // ShareResourceButton() - // av 20250526: - // Public and Authenticated users buttons are - // disabled in this function at the moment because - // providing public or authenticated permissions to - // external resources is not yet implemented in - // [grantPermission()] function. - buttons, - ), - ); - -// ElevatedButton getRetrieveButton( -// BuildContext context, -// String fileName, -// bool isFile, { -// required Future Function( -// String, { -// bool isFile, -// }) onRetrieve, -// }) => -// ElevatedButton( -// child: const Text('Retrieve permissions'), -// onPressed: () async { -// if (fileName.isEmpty) { -// await alert(context, 'Please enter a file name'); -// } else { -// await onRetrieve(fileName, isFile: isFile); -// } -// }, -// ); diff --git a/lib/src/solid/select_recipients.dart b/lib/src/solid/select_recipients.dart index 13bcc23e..4b58382d 100644 --- a/lib/src/solid/select_recipients.dart +++ b/lib/src/solid/select_recipients.dart @@ -186,22 +186,30 @@ class _SelectRecipientsState extends State { Widget build(BuildContext context) { // 20260109 jesscmoore Added capability for granters to share // resources, as well as resource owners - return getButtonContainer( - buttons: widget.isExternalRes - - // TODO jesscmoore 20260118: 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. - - // Recipient type buttons for resource granter - ? selectRecipientButtons(allowedRecipientTypes: granterRecipientTypes) - // Recipient type buttons for resource owner - : selectRecipientButtons(allowedRecipientTypes: ownerRecipientTypes), + return Container( + padding: const EdgeInsets.all(8.0), + height: 100, + child: Row( + children: widget.isExternalRes + + // TODO jesscmoore 20260118: 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. + + // Recipient type buttons for resource granter + ? selectRecipientButtons( + allowedRecipientTypes: granterRecipientTypes, + ) + // Recipient type buttons for resource owner + : selectRecipientButtons( + allowedRecipientTypes: ownerRecipientTypes, + ), + ), ); } } diff --git a/lib/src/solid/show_selected_recipients.dart b/lib/src/solid/show_selected_recipients.dart new file mode 100644 index 00000000..0c2f87f3 --- /dev/null +++ b/lib/src/solid/show_selected_recipients.dart @@ -0,0 +1,101 @@ +/// 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:solidpod/src/solid/constants/web_acl.dart'; + +/// A [StatelessWidget] for showing selected 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. +/// - [updateIndWebIdFunction] - a function to update the selected +/// individual webId. +/// - [updateGroupWebIdFunction] - a function to update the selected +/// group webId list and group name. + +class ShowSelectedRecipients extends StatelessWidget { + const ShowSelectedRecipients({ + super.key, + required this.selectedRecipientType, + required this.selectedRecipientDetails, + }); + + /// Selected recipient + + final RecipientType selectedRecipientType; + + /// Selected recipient details + + final String selectedRecipientDetails; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Text( + 'Recipient/s: ', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + Flexible( + // Show recipients if selected + child: Text( + '${selectedRecipientType.type}${selectedRecipientDetails.isEmpty ? "" : " ($selectedRecipientDetails)"}', + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + // 20251008 gjw Choose blue rather than + // orange which looks red. The red looks + // like it is an error. Blue is more + // neutral. + color: Colors.blueAccent, + ), + ), + ), + ], + ), + ); + } +} From 02002d595ef2627a812061f49e6b60e6ed98835a Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 00:56:50 +1100 Subject: [PATCH 17/29] fix group webid controller --- lib/src/solid/select_recipients.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/src/solid/select_recipients.dart b/lib/src/solid/select_recipients.dart index 4b58382d..9371943d 100644 --- a/lib/src/solid/select_recipients.dart +++ b/lib/src/solid/select_recipients.dart @@ -118,14 +118,6 @@ class SelectRecipients extends StatefulWidget { } class _SelectRecipientsState extends State { - /// Group name text controller - - final groupNameController = TextEditingController(); - - /// Group of webIds text controller - - final groupWebIdsController = TextEditingController(); - /// Define recipient type list List recipientTypeList = []; @@ -142,8 +134,6 @@ class _SelectRecipientsState extends State { @override void dispose() { - groupNameController.dispose(); // Dispose group name editing controller - groupWebIdsController.dispose(); // Dispose group webids editing controller super.dispose(); } From 50b715ef942bac60d1faa960a55269edbe08d796 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 01:28:44 +1100 Subject: [PATCH 18/29] move show selected recipients below selection buttons --- lib/src/solid/grant_permission_ui.dart | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index f349ff89..61d8a576 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -227,6 +227,7 @@ class GrantPermissionUiState extends State // TODO: tidy loadPodData() removing owner and granter webid fetching // to initState + // TODO: move owner and granter setting to ShareResourceButon() /// Runs multiple asynchronous functions to get the data from /// POD server if necessary. @@ -296,15 +297,15 @@ class GrantPermissionUiState extends State ); } - // Load access mode list to be displayed - for (final accessModeStr in widget.accessModeList) { - accessModeList.add(getAccessMode(accessModeStr)); - } + // // Load access mode list to be displayed + // for (final accessModeStr in widget.accessModeList) { + // accessModeList.add(getAccessMode(accessModeStr)); + // } - // Load recipient type list to be displayed - for (final recTypeStr in widget.recipientTypeList) { - recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); - } + // // Load recipient type list to be displayed + // for (final recTypeStr in widget.recipientTypeList) { + // recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); + // } } /// Get new permission and update the permission map From 4402139af30c12b5ade64522dcd45dc39d8d6d05 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 01:36:20 +1100 Subject: [PATCH 19/29] include filename in title and tweak wording in grant permission form --- lib/src/solid/grant_permission_form.dart | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/src/solid/grant_permission_form.dart b/lib/src/solid/grant_permission_form.dart index 23f4aa8e..7c830a89 100644 --- a/lib/src/solid/grant_permission_form.dart +++ b/lib/src/solid/grant_permission_form.dart @@ -297,7 +297,9 @@ class _GrantPermissionFormState extends State { Widget build(BuildContext context) { return AlertDialog( insetPadding: GrantPermFormLayout.contentPadding, - title: const Text('Share resource'), + title: Text( + 'Share ${widget.resourceName}', + ), content: SizedBox( // Use full width on phones, else use a preset narrower width width: @@ -307,13 +309,7 @@ class _GrantPermissionFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ getHeading( - 'Select the recipient/s of file access permissions', - ), - // List selected recipient webids or recipient - // type (public/auth) - ShowSelectedRecipients( - selectedRecipientType: selectedRecipientType, - selectedRecipientDetails: selectedRecipientDetails, + 'Select the recipient/s of file access', ), // Show Select Recipient Buttons @@ -328,9 +324,16 @@ class _GrantPermissionFormState extends State { updateGroupWebIdFunction: updateGroupWebIdInput, ), + // List selected recipient webids or recipient + // type (public/auth) + ShowSelectedRecipients( + selectedRecipientType: selectedRecipientType, + selectedRecipientDetails: selectedRecipientDetails, + ), + smallGapV, getHeading( - 'Select the list of file access permissions', + 'Select one or more file access permissions', ), // Show access mode checkboxes and update // selection status on click From 1afe7f34c60a2a3651ec24b0213fb91a5f81e027 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 01:52:52 +1100 Subject: [PATCH 20/29] adjust wording on sharing page --- lib/src/solid/grant_permission_helper.dart | 13 ++++++++++--- lib/src/solid/grant_permission_ui.dart | 17 +++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/src/solid/grant_permission_helper.dart b/lib/src/solid/grant_permission_helper.dart index 259b8d5b..a556293d 100644 --- a/lib/src/solid/grant_permission_helper.dart +++ b/lib/src/solid/grant_permission_helper.dart @@ -127,9 +127,16 @@ const granterRecipientTypes = [ RecipientType.group, ]; -String getWelcomeStr(String? fileName) => fileName != null - ? 'Share $fileName resource with other PODs' - : 'Share your data resources with other PODs'; +/// Get title of sharing page +String getSharingTitleStr({ + String? fileName, + bool isFile = false, +}) => + fileName != null + ? isFile + ? 'Share $fileName' + : 'Share $fileName folder' + : 'Share your data with other user\'s PODs'; Widget getHeading(String text) => buildHeading( text, diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 61d8a576..55d81e63 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -296,16 +296,6 @@ class GrantPermissionUiState extends State isExternalRes: widget.isExternalRes, ); } - - // // Load access mode list to be displayed - // for (final accessModeStr in widget.accessModeList) { - // accessModeList.add(getAccessMode(accessModeStr)); - // } - - // // Load recipient type list to be displayed - // for (final recTypeStr in widget.recipientTypeList) { - // recipientTypeList.add(RecipientType.getInstanceByValue(recTypeStr)); - // } } /// Get new permission and update the permission map @@ -416,7 +406,10 @@ class GrantPermissionUiState extends State smallGapV, // Sharing heading buildHeading( - getWelcomeStr(widget.resourceName), + getSharingTitleStr( + fileName: widget.resourceName, + isFile: widget.isFile, + ), 22, Colors.blueGrey, ), @@ -449,7 +442,7 @@ class GrantPermissionUiState extends State ), largeGapV, - getHeading('Granted file access permissions'), + getHeading('Current access permissions'), // Permissions table PermissionTable( resourceName: permDataFile, From ceaa20251932da9f3aafd31580dc170b80b1737d Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 13:10:04 +1100 Subject: [PATCH 21/29] create data model for future and reduce lines of GrantPermissionUi() --- lib/src/solid/grant_permission_ui.dart | 141 ++++++++----------- lib/src/solid/models/permission_details.dart | 44 ++++++ lib/src/solid/utils/get_authoriser.dart | 55 ++++++++ 3 files changed, 156 insertions(+), 84 deletions(-) create mode 100644 lib/src/solid/models/permission_details.dart create mode 100644 lib/src/solid/utils/get_authoriser.dart diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 55d81e63..73d89208 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -40,8 +40,9 @@ 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/solid_func_call_status.dart'; +import 'package:solidpod/src/solid/models/permission_details.dart'; import 'package:solidpod/src/solid/utils/alert.dart'; -import 'package:solidpod/src/solid/utils/authdata_manager.dart'; +import 'package:solidpod/src/solid/utils/get_authoriser.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; import 'package:solidpod/src/widgets/app_bar.dart'; import 'package:solidpod/src/widgets/loading_screen.dart'; @@ -70,8 +71,6 @@ import 'package:solidpod/src/widgets/loading_screen.dart'; /// - [onNavigateBack] - Callback function called when navigating back from the screen. class GrantPermissionUi extends StatefulWidget { - /// Initialise widget variables. - const GrantPermissionUi({ required this.child, this.title = 'Demonstrating data sharing functionality', @@ -133,8 +132,7 @@ class GrantPermissionUi extends StatefulWidget { final List accessModeList; - /// The list of types of recipients receiving permission to access the resource. By default all four - /// types of recipient are listed. + /// The list of types of recipients receiving permission to access the resource. By default all four types of recipient are listed. final List recipientTypeList; @@ -220,19 +218,14 @@ class GrantPermissionUiState extends State /// Pod data list retreived as a Future - late Future> podDataList; + late Future podDataList; /// A flag to identify if the resource is a file or not bool isFile = true; - // TODO: tidy loadPodData() removing owner and granter webid fetching - // to initState - // TODO: move owner and granter setting to ShareResourceButon() - - /// Runs multiple asynchronous functions to get the data from - /// POD server if necessary. + /// Gets permission details data from POD server if necessary. - Future> loadPodData( + Future loadPodData( String resName, { bool isFile = true, bool isExternalRes = false, @@ -246,32 +239,28 @@ class GrantPermissionUiState extends State switch (response) { case SolidFunctionCallStatus.aclFound: - // TODO: move readPermission(), getting owner and granter into - // new getPermissionDetails() to get the permission map, owner - // and granter of a resource. + // Permission map from ACL of resource final Map result = await readPermission( fileName: resName, isFile: isFile, isExternalRes: widget.isExternalRes, ); - // TODO: write get_authoriser.dart with functions to get - // ownerWebId and granterWebId - - // Fetch owner's webID - // ownerWebId == userWebId if not externally owned resource - - final ownerWebId = widget.isExternalRes - ? widget.ownerWebId - : await AuthDataManager.getWebId(); - // Fetch granter's webID - // granterWebId == userWebId if not externally owned resource - - final granterWebId = widget.isExternalRes - ? widget.granterWebId - : await AuthDataManager.getWebId(); + // 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: widget.isExternalRes, + webId: widget.ownerWebId, + ), + granterWebId: await getAuthoriser( + isExternalRes: widget.isExternalRes, + webId: widget.granterWebId, + ), + ); - return [result, ownerWebId, granterWebId]; + return permissionDetails; case SolidFunctionCallStatus.notLoggedIn: await _alert('Please login first to retrieve permission'); @@ -282,13 +271,14 @@ class GrantPermissionUiState extends State default: await _alert('Unknown error'); } - return []; + + return null; } @override void initState() { super.initState(); - // Load future + // Load permission map from ACL, owner and granter web ids if (widget.resourceName != null) { podDataList = loadPodData( widget.resourceName as String, @@ -298,54 +288,50 @@ class GrantPermissionUiState extends State } } - /// Get new permission and update the permission map + /// Update the permission data map Future _updatePermissions( String fileName, { bool isFile = true, bool isExternalRes = false, }) async { - debugPrint('_updatePermissions(): ...'); final pdata = await loadPodData( fileName, isFile: isFile, isExternalRes: isExternalRes, ); - if (pdata.isNotEmpty) { - assert(pdata.length == 3); - final permissionMap = pdata.first; - final ownerWebId = pdata[1]; - final granterWebId = pdata.last; - - if (permissionMap.isEmpty) { - await _alert('We could not find a resource by the name $fileName'); - } else { - setState(() { - permDataMap = permissionMap; - permDataFile = fileName; - _ownerWebId = ownerWebId as String; - _granterWebId = granterWebId as String; - }); - } + + 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; + }); } + // } } /// Private function to call alert dialog in grant permission UI context Future _alert(String msg) async => alert(context, msg); - // TODO: rename futureObjList to dataMap - /// Build the main widget - Widget _buildPermPage(BuildContext context, [List? futureObjList]) { + Widget _buildPermPage( + BuildContext context, [ + PermissionDetails? futurePermDetails, + ]) { /// Controller for vertical page scrolling final pageScrollController = ScrollController(); // Check if future is set or not. If set display the permission map - if (futureObjList != null && pageInitialied == false) { - permDataMap = futureObjList.first as Map; - _ownerWebId = futureObjList[1] as String; - _granterWebId = futureObjList.last as String; - // FIXME: ensure permDataFile updates within _updatePermissions() to be correct when resource selected within GrantPermissionUi() + if (futurePermDetails != null && pageInitialied == false) { + permDataMap = futurePermDetails.permissionMap; + _ownerWebId = futurePermDetails.ownerWebId; + _granterWebId = futurePermDetails.granterWebId; permDataFile = widget.resourceName!; pageInitialied = true; } @@ -357,6 +343,7 @@ class GrantPermissionUiState extends State if (fileName.isEmpty) { await _alert('Please enter a file name'); } else { + // TODO: jesscmoore 20260119 check for resource selected in UI await _updatePermissions( fileName, isFile: isFile, @@ -385,15 +372,7 @@ class GrantPermissionUiState extends State // not provided appBar: widget.showAppBar ? customAppBar : null, - // Make Grant Permission UI vertically scrollable - // Shows when content exceeds display height - body: Scrollbar( - // 20250722 jm: - // For scrollbar visibility before scrolling, - // set to true, or set property to true - // in parent app MaterialApp(theme: ThemeData(scrollbarTheme: scrollbarTheme: ScrollbarThemeData( - // thumbVisibility: WidgetStateProperty.all(true))) thumbVisibility: true, // show before user starts scrolling controller: pageScrollController, child: SingleChildScrollView( @@ -426,7 +405,6 @@ class GrantPermissionUiState extends State smallGapV, retrievePermissionButton, ], - // Share resource button ShareResourceButton( resourceName: widget.resourceName, fileNameController: fileNameController, @@ -443,7 +421,6 @@ class GrantPermissionUiState extends State largeGapV, getHeading('Current access permissions'), - // Permissions table PermissionTable( resourceName: permDataFile, permDataMap: permDataMap, @@ -463,18 +440,14 @@ class GrantPermissionUiState extends State } @override - Widget build(BuildContext context) => - // Build as a separate widget with the possibility of adding a FutureBuilder - // in the Future - - widget.resourceName == null - ? _buildPermPage(context) - : FutureBuilder( - future: podDataList, - builder: (context, snapshot) => snapshot.hasData - ? snapshot.data!.first == SolidFunctionCallStatus.notLoggedIn - ? widget.child - : _buildPermPage(context, snapshot.data) - : Scaffold(body: loadingScreen(normalLoadingScreenHeight)), - ); + Widget build(BuildContext context) => widget.resourceName == null + ? _buildPermPage(context) + : FutureBuilder( + future: podDataList, + builder: (context, snapshot) => snapshot.hasData + ? snapshot.data!.permissionMap.isEmpty + ? widget.child + : _buildPermPage(context, snapshot.data) + : Scaffold(body: loadingScreen(normalLoadingScreenHeight)), + ); } diff --git a/lib/src/solid/models/permission_details.dart b/lib/src/solid/models/permission_details.dart new file mode 100644 index 00000000..11f9f55f --- /dev/null +++ b/lib/src/solid/models/permission_details.dart @@ -0,0 +1,44 @@ +/// Data model for permission details +/// +/// 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: Jess Moore + +library; + +/// Data model for permission details retrieved in GrantPermissionUi() +/// from ACL file, and fetching ownerWebId and granterWebId if provided or deriving them. + +class PermissionDetails { + final Map permissionMap; + final String ownerWebId; + final String granterWebId; + + const PermissionDetails({ + required this.permissionMap, + required this.ownerWebId, + required this.granterWebId, + }); +} diff --git a/lib/src/solid/utils/get_authoriser.dart b/lib/src/solid/utils/get_authoriser.dart new file mode 100644 index 00000000..15743391 --- /dev/null +++ b/lib/src/solid/utils/get_authoriser.dart @@ -0,0 +1,55 @@ +/// 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 'dart:core'; + +import 'package:solidpod/src/solid/utils/authdata_manager.dart'; + +/// Standardise retrieval of authoriser (ownerWebId or granterWebId) +/// for a resource in GrantPermissionUi +/// +/// Parameters: +/// - [isExternalRes] - flag describing whether [fileName] being shared is a file. +/// - [webId] - use webId if provided, else fetch user webId. + +Future getAuthoriser({ + bool isExternalRes = false, + String? webId, +}) async { + assert( + // Requires ownerWebId and granterWebId if resource + // is an externally owned. + isExternalRes == false || webId != null, + 'ownerWebId and granterWebId must be provided if isExternalRes == true', + ); + return isExternalRes ? webId! : await AuthDataManager.getWebId() as String; +} From e6746298c4f6baea277a9131c024e2bd8897618a Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 13:10:18 +1100 Subject: [PATCH 22/29] update note --- lib/src/solid/select_recipients.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/solid/select_recipients.dart b/lib/src/solid/select_recipients.dart index 9371943d..57bd409d 100644 --- a/lib/src/solid/select_recipients.dart +++ b/lib/src/solid/select_recipients.dart @@ -182,8 +182,8 @@ class _SelectRecipientsState extends State { child: Row( children: widget.isExternalRes - // TODO jesscmoore 20260118: check grant/revoke to public/auth - // works on external resources + // jesscmoore 20260118: requires check grant/revoke to + // public/auth works on external resources // av 20250526: // Public and Authenticated recipient buttons are // disabled currently because From a384abd37ce1c583b4e0655c0db5b28f2ba2e8a4 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 13:28:43 +1100 Subject: [PATCH 23/29] fix import order --- lib/src/solid/grant_permission_ui.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index 73d89208..a646394b 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -36,11 +36,11 @@ import 'package:solidpod/src/solid/chk_exists_and_has_acl.dart'; import 'package:solidpod/src/solid/constants/ui.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/permission_details.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/solid_func_call_status.dart'; -import 'package:solidpod/src/solid/models/permission_details.dart'; import 'package:solidpod/src/solid/utils/alert.dart'; import 'package:solidpod/src/solid/utils/get_authoriser.dart'; import 'package:solidpod/src/solid/utils/heading.dart'; From cac4ed1ef7207a083c76722897d2f9528e80fd8b Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 13:58:09 +1100 Subject: [PATCH 24/29] remove print statement --- lib/src/solid/share_resource_button.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/solid/share_resource_button.dart b/lib/src/solid/share_resource_button.dart index 9c3654c7..9b9304f5 100644 --- a/lib/src/solid/share_resource_button.dart +++ b/lib/src/solid/share_resource_button.dart @@ -162,8 +162,6 @@ class _ShareResourceButtonState extends State { /// Mark permissions as granted successfully for callback tracking Future _updatePermissionGrantedStatus() async { - debugPrint('_updatePermissionGrantedStatus(): ...'); - setState(() => permissionsGrantedSuccessfully = true); } From 63051e454b0b910009f6a093e20771a42de58775 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 15:27:21 +1100 Subject: [PATCH 25/29] fixed to work in add/delete permission from any resource --- lib/src/solid/grant_permission_ui.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index a646394b..b7c6621b 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -233,7 +233,7 @@ class GrantPermissionUiState extends State final SolidFunctionCallStatus response = await chkExistsAndHasAcl( fileName: resName, isFile: isFile, - isExternalRes: widget.isExternalRes, + isExternalRes: isExternalRes, ); switch (response) { @@ -243,7 +243,7 @@ class GrantPermissionUiState extends State final Map result = await readPermission( fileName: resName, isFile: isFile, - isExternalRes: widget.isExternalRes, + isExternalRes: isExternalRes, ); // Permission Details object to store permission map from ACL, and owner @@ -251,11 +251,11 @@ class GrantPermissionUiState extends State final permissionDetails = PermissionDetails( permissionMap: result, ownerWebId: await getAuthoriser( - isExternalRes: widget.isExternalRes, + isExternalRes: isExternalRes, webId: widget.ownerWebId, ), granterWebId: await getAuthoriser( - isExternalRes: widget.isExternalRes, + isExternalRes: isExternalRes, webId: widget.granterWebId, ), ); @@ -343,11 +343,9 @@ class GrantPermissionUiState extends State if (fileName.isEmpty) { await _alert('Please enter a file name'); } else { - // TODO: jesscmoore 20260119 check for resource selected in UI await _updatePermissions( fileName, isFile: isFile, - isExternalRes: widget.isExternalRes, ); } }, From 2b86cef18625cbeb7db380101226572ffa3ca38a Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 15:34:14 +1100 Subject: [PATCH 26/29] only show retrievePermission button if empty --- lib/src/solid/grant_permission_ui.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/solid/grant_permission_ui.dart b/lib/src/solid/grant_permission_ui.dart index b7c6621b..7107f856 100644 --- a/lib/src/solid/grant_permission_ui.dart +++ b/lib/src/solid/grant_permission_ui.dart @@ -400,8 +400,10 @@ class GrantPermissionUiState extends State onResourceTypeChange: (bool v) => setState(() => isFile = v), ), - smallGapV, - retrievePermissionButton, + if (permDataMap.isEmpty) ...[ + smallGapV, + retrievePermissionButton, + ], ], ShareResourceButton( resourceName: widget.resourceName, From 4bfd1d286eb70d2caf82e449813f1fc0742f9f33 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 15:35:22 +1100 Subject: [PATCH 27/29] updated .metadata file --- example/.metadata | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/.metadata b/example/.metadata index 2e80cfec..644060a6 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" + revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: windows - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: web + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 # User provided section From a8a21935ad7a9d06ed86084616c767d8fbd8a3a3 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 15:51:21 +1100 Subject: [PATCH 28/29] tweak assert message --- lib/src/solid/utils/get_authoriser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/solid/utils/get_authoriser.dart b/lib/src/solid/utils/get_authoriser.dart index 15743391..7eacb1a1 100644 --- a/lib/src/solid/utils/get_authoriser.dart +++ b/lib/src/solid/utils/get_authoriser.dart @@ -49,7 +49,7 @@ Future getAuthoriser({ // Requires ownerWebId and granterWebId if resource // is an externally owned. isExternalRes == false || webId != null, - 'ownerWebId and granterWebId must be provided if isExternalRes == true', + 'webId must be provided if isExternalRes == true', ); return isExternalRes ? webId! : await AuthDataManager.getWebId() as String; } From ecb4e481cb47ad6562398ba8ea024ec126371381 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Mon, 19 Jan 2026 16:17:16 +1100 Subject: [PATCH 29/29] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9acd777..25413d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod). ## 0.10 Further UI migrations ++ Move grant permission form to dialog [0.9.12 20260119 jesscmoore] + Standardise action message colors [0.9.11 20260116 jesscmoore] + Fixed sharing to public/auth users bug [0.9.10 20260115 jesscmoore] + Fixed error when reading large file as single chunk [0.9.9 20260117 cdawei]