Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6df8491
support performing permission granting on list of resources
jesscmoore Apr 8, 2026
ea8fa3c
default to showing current permissions
jesscmoore Apr 8, 2026
3f50859
adjust the share button label and share file title
jesscmoore Apr 8, 2026
3afd921
left align the resource/resource list, and add drop down to select re…
jesscmoore Apr 8, 2026
6efb296
added slider to show short or full paths
jesscmoore Apr 8, 2026
1b7a4c7
coloured the dropdown to align with style
jesscmoore Apr 8, 2026
d61edda
updated docstring for GrantPermissionUI
jesscmoore Apr 8, 2026
d53f910
fix background of sharing page to be transparent or use theme background
jesscmoore Apr 8, 2026
3f2a855
set switches to use theme switch active colour or actionColors.success
jesscmoore Apr 8, 2026
8236855
use elevated button theme with optional color parameter for share button
jesscmoore Apr 8, 2026
f3874b2
simplify sharing title
jesscmoore Apr 8, 2026
f7a2eb6
added styling
jesscmoore Apr 8, 2026
b770346
show permission table/history if a single/seleced resource
jesscmoore Apr 9, 2026
a117d20
fix scrolling
jesscmoore Apr 9, 2026
5b6e6f0
adjust scrollbar on resource list and padding
jesscmoore Apr 9, 2026
be0a940
right align the retrieve permission button for consistency
jesscmoore Apr 9, 2026
65bb497
fixed permission granting in manage permissions for any resource demo…
jesscmoore Apr 9, 2026
36cc1ac
fixed GrantPermissionUi() to work when resource set by user in demopod
jesscmoore Apr 9, 2026
4eb6595
refactored GrantPermissionUIState into smaller files
jesscmoore Apr 9, 2026
d11424b
linted
jesscmoore Apr 9, 2026
067ed36
fix import order
jesscmoore Apr 9, 2026
288f6e8
Merge branch 'dev' into jess/271_multi_share
jesscmoore Apr 11, 2026
40eb4c2
fix typo in CHANGELOD
jesscmoore Apr 11, 2026
641c97a
refactor to remove redundant resourceName parameter
jesscmoore Apr 11, 2026
e64f654
added search bar for current permissions view so that search on both …
jesscmoore Apr 11, 2026
fa6c972
fixed bug in permission callback demo
jesscmoore Apr 11, 2026
c59ac66
update example app to fetch latest solidpod
jesscmoore Apr 14, 2026
23d8fb7
when titleData provided, show a widget ot choose url/filename/title
jesscmoore Apr 14, 2026
04a70c8
fix import order
jesscmoore Apr 14, 2026
1993717
show title by default
jesscmoore Apr 14, 2026
c2b0d1e
display radio group horizontally
jesscmoore Apr 14, 2026
beb3b71
linted
jesscmoore Apr 14, 2026
57d36ff
wrap info line
jesscmoore Apr 14, 2026
93ef303
stack widgets vertically in narrow displays
jesscmoore Apr 14, 2026
2dd9aff
explained how to show title in GrantPermissionUi()
jesscmoore Apr 14, 2026
3c7622a
Merge branch 'dev' into jess/271_multi_share
jesscmoore Apr 15, 2026
b1c9c98
tune permission history title
jesscmoore Apr 17, 2026
9afd5f9
corrected title in the grant permission dialog
jesscmoore Apr 17, 2026
0a9e30c
setup permissions as a separate page from sharing page
jesscmoore Apr 18, 2026
7c2e159
extract resource display mode widget to display on permission page also
jesscmoore Apr 18, 2026
3563cc7
align the resource mode display widget
jesscmoore Apr 18, 2026
1e4b8e8
position view permission button next to share resources button
jesscmoore Apr 18, 2026
0d8ec10
give sharing resource list a scroll controller
jesscmoore Apr 18, 2026
7632eb1
linted
jesscmoore Apr 18, 2026
ea2d63d
Fixed textcolor null error in resource drop dwn
jesscmoore Apr 18, 2026
80acf43
tidy up
jesscmoore Apr 18, 2026
09cefce
tidy up
jesscmoore Apr 18, 2026
f2a7017
Merge branch 'dev' into jess/271_multi_share
gjwgit Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,13 @@ ElevatedButton(
The `GrantPermissionUi` widget provides a full-featured page for
granting, editing, and revoking access permissions on resources stored
in a Solid POD. Wrap it inside a navigation action to reach the
permission management page.
permission management page. The titleData parameter, if provides,
adds support for switch between file url, filename and file title.

### Basic usage (browse all resources)
### Basic usage to grant/revoke/change permissions for any resource

This allows the user to select the resource, before inspecting their permissions
and granting, revoking or editing permissions.

```dart
ElevatedButton(
Expand All @@ -853,7 +857,7 @@ ElevatedButton(
)
```

### Permissions for a specific file
### Grant/revoke/change permissions for a specific file

```dart
ElevatedButton(
Expand All @@ -862,15 +866,15 @@ ElevatedButton(
context,
MaterialPageRoute(
builder: (context) => const GrantPermissionUi(
resourceName: 'my-data-file.ttl',
resourceNames: ['my-data-file.ttl'],
child: ReturnPage(),
),
),
),
)
```

### Permissions for a specific directory
### Grant/revoke/change permissions for a specific directory

```dart
ElevatedButton(
Expand All @@ -879,7 +883,7 @@ ElevatedButton(
context,
MaterialPageRoute(
builder: (context) => const GrantPermissionUi(
resourceName: 'parentDir/',
resourceNames: ['parentDir/'],
child: ReturnPage(),
isFile: false,
),
Expand All @@ -888,7 +892,7 @@ ElevatedButton(
)
```

### Permissions for an externally owned resource
### Grant/revoke/change permissions for an externally owned resource

When the user has *control* access to a resource owned by someone else:

Expand All @@ -899,7 +903,7 @@ ElevatedButton(
context,
MaterialPageRoute(
builder: (context) => GrantPermissionUi(
resourceName: 'my-data-file.ttl',
resourceNames: const ['my-data-file.ttl'],
isExternalRes: true,
ownerWebId: ownerWebId,
granterWebId: granterWebId,
Expand All @@ -910,6 +914,28 @@ ElevatedButton(
)
```

### Grant permissions for multiple user owned resources

When the user wants to apply the same grant permission operation on
a list of resources:

```dart
ElevatedButton(
child: const Text('Add/Delete Permissions for Multiple Files'),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GrantPermissionUi(
resourceNames: const ['my-data-file1.ttl', 'my-data-file2.ttl', 'my-data-file3.ttl'],
ownerWebId: ownerWebId,
granterWebId: granterWebId,
child: ReturnPage(),
),
),
),
)
```

## View Permission UI Example

The `SharedResourcesUi` widget displays the resources that have been
Expand Down
5 changes: 2 additions & 3 deletions example/lib/features/permission_callback_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ class _PermissionCallbackDemoState extends State<PermissionCallbackDemo> {
try {
for (int i = 0; i < _sampleFiles.length; i++) {
final fileName = _sampleFiles[i];
final filePath = [await getDataDirPath(), fileName].join('/');

// Create rich demo content with different data for each file
final fileNumber = i + 1;
Expand All @@ -100,7 +99,7 @@ demo:exampleData$fileNumber

if (!mounted) return;

await writePod(filePath, demoContent);
await writePod(fileName, demoContent);
}
} catch (e) {
debugPrint('❌ [CallbackDemo] Error creating demo files: $e');
Expand Down Expand Up @@ -157,7 +156,7 @@ demo:exampleData$fileNumber
foregroundColor: Theme.of(context).appBarTheme.foregroundColor,
),
body: GrantPermissionUi(
resourceName: _sampleFiles[_currentFileIndex],
resourceNames: [_sampleFiles[_currentFileIndex]],
title: 'Demo: Grant Permission with Callback',
accessModeList: const ['read'], // Simplified for demo.
recipientTypeList: const ['indi'], // Individual permissions only.
Expand Down
2 changes: 1 addition & 1 deletion example/lib/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ class HomeState extends State<Home> with SingleTickerProviderStateMixin {
builder: (context) => const SolidScaffold(
body: GrantPermissionUi(
backgroundColor: titleBackgroundColor,
resourceName: 'keyvalue/key-value.ttl',
resourceNames: ['keyvalue/key-value.ttl'],
child: Home(),
),
),
Expand Down
20 changes: 20 additions & 0 deletions lib/src/utils/path_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ class PathUtils {
return normalised.substring(0, lastSlash);
}

/// Returns the display label for a resource name, respecting the current
/// display mode.
///
/// - When [showTitle] is true and [titleData] contains [name], the mapped
/// title is returned; otherwise falls back to [name].
/// - When [showFullPath] is true the full [name] is returned.
/// - Otherwise the last path segment (basename) is returned.

static String resourceDisplayName(
String name, {
required bool showFullPath,
bool showTitle = false,
Map<String, String>? titleData,
}) {
if (showTitle && titleData != null) {
return titleData[name] ?? basename(name);
}
return showFullPath ? name : basename(name);
}

/// Gets the last segment (file or directory name) of a path.
///
/// Examples:
Expand Down
61 changes: 41 additions & 20 deletions lib/src/widgets/grant_permission_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import 'package:solidui/solidui.dart'
updatePermissionMsg;
import 'package:solidui/src/utils/snack_bar.dart';
import 'package:solidui/src/utils/solid_alert.dart';
import 'package:solidui/src/widgets/grant_permission_helpers_ui.dart';
import 'package:solidui/src/widgets/group_webid_input.dart';
import 'package:solidui/src/widgets/ind_webid_input_screen.dart';
import 'package:solidui/src/widgets/select_recipients.dart';
Expand All @@ -62,7 +63,9 @@ import 'package:solidui/src/widgets/show_selected_recipients.dart';
/// provided [resourceName]
///
/// Parameters:
/// - [resourceName] - The filename or file url of the resource. If [isExternalRes], it should be the url of the resource.
/// - [resourceNames] - List of resource names. The first entry is used for
/// display in the dialog title and ACL table refresh. All entries receive
/// the same permission grant.
/// - [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.
Expand All @@ -84,9 +87,11 @@ class GrantPermissionForm extends StatefulWidget {

final String granterWebId;

/// The name of the file or directory that access is being granted for.
/// List of resource names to grant permission to. The first entry is used
/// for display in the dialog title and ACL table refresh after granting.
/// All entries receive the same permission grant sequentially.

final String resourceName;
final List<String> resourceNames;

final bool isExternalRes;

Expand Down Expand Up @@ -127,7 +132,7 @@ class GrantPermissionForm extends StatefulWidget {
const GrantPermissionForm({
super.key,
required this.updatePermissionsFunction,
required this.resourceName,
required this.resourceNames,
required this.ownerWebId,
required this.granterWebId,
this.accessModeList = const ['read', 'write', 'append', 'control'],
Expand Down Expand Up @@ -289,7 +294,13 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {
Widget build(BuildContext context) {
return AlertDialog(
insetPadding: GrantPermFormLayout.contentPadding,
title: Text('Share ${widget.resourceName}'),
title: Text(
makeSharingTitleStr(
resourceNames: widget.resourceNames,
isFile: widget.isFile,
),
style: Theme.of(context).textTheme.titleLarge,
),
content: Scrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
Expand Down Expand Up @@ -359,20 +370,30 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {

if (selectedRecipientType.type.isNotEmpty) {
if (selectedPermList.isNotEmpty) {
SolidFunctionCallStatus result;
// Grant permission for each resource sequentially.
// When resourceNames is provided all resources share the
// same recipient and permission selections.
final resourcesToGrant = widget.resourceNames;
SolidFunctionCallStatus result =
SolidFunctionCallStatus.success;
try {
// Update ACL and permission logs to grant permission
result = await grantPermission(
fileName: widget.resourceName,
isFile: widget.isFile,
permissionList: selectedPermList,
recipientType: selectedRecipientType,
recipientWebIdList: finalWebIdList,
ownerWebId: widget.ownerWebId,
granterWebId: widget.granterWebId,
isExternalRes: widget.isExternalRes,
groupName: selectedGroupName,
);
for (final name in resourcesToGrant) {
final r = await grantPermission(
fileName: name,
isFile: widget.isFile,
permissionList: selectedPermList,
recipientType: selectedRecipientType,
recipientWebIdList: finalWebIdList,
ownerWebId: widget.ownerWebId,
granterWebId: widget.granterWebId,
isExternalRes: widget.isExternalRes,
groupName: selectedGroupName,
);
if (r != SolidFunctionCallStatus.success) {
result = r;
break;
}
}

// Close grant permission dialog
if (!context.mounted) return;
Expand All @@ -386,7 +407,7 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {
_showSnackBar(successMsg, ActionColors.success);
// Update permissions table
await widget.updatePermissionsFunction(
widget.resourceName, //_resourceName,
widget.resourceNames.first,
isFile: widget.isFile,
isExternalRes: widget.isExternalRes,
);
Expand All @@ -402,7 +423,7 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {

// Also log to console for debugging
debugPrintFailure(
widget.resourceName, // _resourceName,
widget.resourceNames.first,
finalWebIdList,
selectedPermList,
);
Expand Down
55 changes: 37 additions & 18 deletions lib/src/widgets/grant_permission_helpers_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import 'package:flutter/material.dart';

import 'package:solidpod/solidpod.dart' show AccessMode, RecipientType;

import 'package:solidui/src/constants/ui_colors.dart' show ActionColors;
import 'package:solidui/src/constants/ui_layout.dart' show SharingPageLayout;
import 'package:solidui/src/widgets/permission_checkbox.dart';

Expand Down Expand Up @@ -125,12 +126,17 @@ const ownerRecipientTypes = [
const granterRecipientTypes = [RecipientType.individual, RecipientType.group];

/// Get title of sharing page.
String makeSharingTitleStr({String? fileName, bool isFile = false}) =>
fileName != null
? isFile
? 'Share $fileName'
: 'Share $fileName folder'
: 'Share your data with other user\'s PODs';
String makeSharingTitleStr({
List<String>? resourceNames,
bool isFile = false,
}) {
if (resourceNames != null && resourceNames.length > 1) {
Comment thread
jesscmoore marked this conversation as resolved.
return isFile ? 'Sharing multiple files' : 'Sharing multiple folders';
} else if (resourceNames != null) {
return isFile ? 'Sharing file' : 'Sharing folder';
}
return 'Share your data with other user\'s PODs';
}

// Widget builders

Expand Down Expand Up @@ -168,18 +174,31 @@ Widget getResourceForm({
(value == null || value.isEmpty) ? 'Empty field' : null,
),
const SizedBox(height: 10),
SwitchListTile(
title: const Text(
'Is a File?',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
subtitle: Text(isFile ? 'Yes' : 'No'),
value: isFile,
onChanged: onResourceTypeChange,
thumbColor: WidgetStateProperty.resolveWith<Color?>(
(Set<WidgetState> states) =>
states.contains(WidgetState.selected) ? Colors.green : null,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Text(
'Is a File?',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text(isFile ? 'Yes' : 'No'),
],
),
Builder(
builder: (context) => Switch(
value: isFile,
activeThumbColor:
Theme.of(context).switchTheme.thumbColor?.resolve(
{WidgetState.selected},
) ??
ActionColors.success,
onChanged: onResourceTypeChange,
),
),
],
),
],
),
Expand Down
Loading
Loading