Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The package is available from

## 1.1 Consolidate Android Login

+ Confirm switch public to private sharing [1.0.26 20260728 jesscmoore]
+ Add sharing permission recipientType callback [1.0.25 20260728 jesscmoore]
+ Support webid editing to link a another Pod [1.0.24 20260726 jesscmoore]
+ Bug fix auto-login when no domain folder on server [1.0.23 20260729 gjw]
+ Add webid to profile [1.0.22 20260726 jesscmoore]
Expand Down
81 changes: 81 additions & 0 deletions lib/src/widgets/grant_permission_dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,87 @@ Future<bool> confirmPublicSharingDecryption(
return confirmed ?? false;
}

/// Confirm with the user before an individual/group grant revokes an
/// existing Public/Authenticated User class grant and re-encrypts the
/// resource (see `grantPermission`'s `revokePublicAccessOnSpecificGrant`).
///
/// [existingClasses] is whichever of [RecipientType.public]/`.authUser`
/// currently have a grant on the resource (one, the other, or both) —
/// used to word the "public"/"auth user" and "publicly"/"to all
/// authenticated users" phrases correctly.

Future<bool> confirmRevokeSharedAccessForSpecificGrant(
BuildContext context,
Set<RecipientType> existingClasses,
RecipientType newRecipientType,
String recipientLabel,
) async {
final accessLabel = [
if (existingClasses.contains(RecipientType.public)) 'public',
if (existingClasses.contains(RecipientType.authUser)) 'signed-in user',
].join('/');
final audience = [
if (existingClasses.contains(RecipientType.public)) 'publicly',
if (existingClasses.contains(RecipientType.authUser))
'to all signed-in users',
].join(' and ');
final recipientKind =
newRecipientType == RecipientType.individual ? 'individual' : 'group';

final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(
'Revoke $accessLabel access and share privately to $recipientLabel?',
),
content: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: alertMaxWidthForCharsPerLine(defaultAlertMaxCharsPerLine),
),
child: Text(
'This file is currently shared $audience. The $recipientKind you '
'wish to grant access to currently already has access. '
'Do you want to remove $accessLabel access and '
'just share privately to $recipientLabel?',
),
),
actions: [
MarkdownTooltip(
message: '''

**Cancel**

Close this dialog without changing the file. Your file remains
decrypted and shared $audience.

''',
child: TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
),
),
MarkdownTooltip(
message: '''

**Share Privately**

Encrypt this file in your POD, share privately, and revoke access to public and signed-in users.

''',
child: TextButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
style: TextButton.styleFrom(
foregroundColor: ActionColors.warning,
),
child: const Text('Share Privately'),
),
),
],
),
);
return confirmed ?? false;
}

/// Priority order used when several WebIDs in the group list fail. We
/// surface a single dialog and prefer the most actionable failure mode.

Expand Down
59 changes: 59 additions & 0 deletions lib/src/widgets/grant_permission_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import 'package:solidui/solidui.dart'
successMsg,
updatePermissionMsg;
import 'package:solidui/src/utils/solid_alert.dart';
import 'package:solidui/src/utils/web_id_parser.dart' show WebIdParts;
import 'package:solidui/src/utils/webid_message.dart' show webIdCheckMessage;
import 'package:solidui/src/widgets/grant_permission_dialogs.dart';
import 'package:solidui/src/widgets/grant_permission_helpers_ui.dart';
Expand Down Expand Up @@ -134,6 +135,15 @@ class GrantPermissionForm extends StatefulWidget {

final VoidCallback? onPermissionGranted;

/// Callback called when permissions are granted successfully, with the
/// [RecipientType] and resource names that were just granted. Fires
/// alongside [onPermissionGranted] — added so callers can distinguish a
/// public/authenticated-user grant from an individual/group grant, which
/// the plain [onPermissionGranted] cannot do.

final void Function(RecipientType recipientType, List<String> resourceNames)?
onRecipientTypeGranted;

/// Optional human-readable name for the resource, used in notification
/// messages sent to recipients upon successful permission granting.

Expand All @@ -159,6 +169,7 @@ class GrantPermissionForm extends StatefulWidget {
required this.updatePermissionGrantedFunction,
this.dataFilesMap = const {},
this.onPermissionGranted,
this.onRecipientTypeGranted,
this.resourceDisplayName,
this.inviteConfig,
});
Expand Down Expand Up @@ -549,6 +560,50 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {
}
if (!context.mounted) return;

// Granting to a specific individual/group revokes any existing
// Public/Authenticated User grant and re-encrypts the resource
// (see `grantPermission`'s `revokePublicAccessOnSpecificGrant`).
// Confirm with the user before doing so, mirroring the warning
// above for the reverse direction.
if (selectedRecipientType == RecipientType.individual ||
selectedRecipientType == RecipientType.group) {
final existingClassPerms = <RecipientType, List<String>>{};
for (final name in widget.resourceNames) {
existingClassPerms.addAll(
await getUserClassPermissions(
fileName: name,
isFile: widget.isFile,
isExternalRes: widget.isExternalRes,
),
);
}
if (!context.mounted) return;
if (existingClassPerms.isNotEmpty) {
final recipientLabel =
selectedRecipientType == RecipientType.individual
? (finalWebIdList.isNotEmpty
? (WebIdParts.tryParse(
finalWebIdList.first.toString(),
)?.username ??
finalWebIdList.first.toString())
: 'this recipient')
: selectedGroupName;
final proceed = await confirmRevokeSharedAccessForSpecificGrant(
context,
existingClassPerms.keys.toSet(),
selectedRecipientType,
recipientLabel,
);
if (!context.mounted) return;
if (!proceed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Action cancelled')),
);
return;
}
}
}

// Capture the ScaffoldMessenger now, while the dialog and its host
// page are still mounted, so the success feedback can be shown
// after this dialog is popped.
Expand Down Expand Up @@ -635,6 +690,10 @@ class _GrantPermissionFormState extends State<GrantPermissionForm> {

// Trigger the onPermissionGranted callback if provided
widget.onPermissionGranted?.call();
widget.onRecipientTypeGranted?.call(
selectedRecipientType,
widget.resourceNames,
);
} else if (result == SolidFunctionCallStatus.fail) {
if (!context.mounted) return;
await showGrantPermissionErrorDialog(
Expand Down
8 changes: 8 additions & 0 deletions lib/src/widgets/grant_permission_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class GrantPermissionUi extends StatefulWidget {
this.buttonColor,
this.customAppBar,
this.onPermissionGranted,
this.onRecipientTypeGranted,
this.onNavigateBack,
this.resourceDisplayName,
this.shareButtonColor,
Expand Down Expand Up @@ -191,6 +192,13 @@ class GrantPermissionUi extends StatefulWidget {

final VoidCallback? onPermissionGranted;

/// Callback called when permissions are granted successfully, with the
/// [RecipientType] and resource names that were just granted. See
/// [GrantPermissionForm.onRecipientTypeGranted].

final void Function(RecipientType recipientType, List<String> resourceNames)?
onRecipientTypeGranted;

/// Callback function called when navigating back from the screen.

final VoidCallback? onNavigateBack;
Expand Down
1 change: 1 addition & 0 deletions lib/src/widgets/grant_permission_ui_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ class GrantPermissionUiState extends State<GrantPermissionUi>
isFile: getIsFile(),
dataFilesMap: widget.dataFilesMap,
onPermissionGranted: widget.onPermissionGranted,
onRecipientTypeGranted: widget.onRecipientTypeGranted,
resourceDisplayName: widget.resourceDisplayName,
buttonColor:
widget.shareButtonColor ?? widget.buttonColor,
Expand Down
11 changes: 11 additions & 0 deletions lib/src/widgets/share_resource_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ library;

import 'package:flutter/material.dart';

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

import 'package:solidui/src/utils/solid_alert.dart';
import 'package:solidui/src/widgets/grant_permission_form.dart';
import 'package:solidui/src/widgets/solid_invite_others_models.dart';
Expand Down Expand Up @@ -108,6 +110,13 @@ class ShareResourceButton extends StatefulWidget {

final VoidCallback? onPermissionGranted;

/// Callback called when permissions are granted successfully, with the
/// [RecipientType] and resource names that were just granted. See
/// [GrantPermissionForm.onRecipientTypeGranted].

final void Function(RecipientType recipientType, List<String> resourceNames)?
onRecipientTypeGranted;

/// Optional human-readable name for the resource, used in notification
/// messages sent to recipients upon successful permission granting.

Expand Down Expand Up @@ -138,6 +147,7 @@ class ShareResourceButton extends StatefulWidget {
required this.isFile,
this.dataFilesMap = const {},
this.onPermissionGranted,
this.onRecipientTypeGranted,
this.resourceDisplayName,
this.buttonColor,
this.inviteConfig,
Expand Down Expand Up @@ -225,6 +235,7 @@ class _ShareResourceButtonState extends State<ShareResourceButton> {
dataFilesMap: widget.dataFilesMap,
updatePermissionGrantedFunction: _updatePermissionGrantedStatus,
onPermissionGranted: widget.onPermissionGranted,
onRecipientTypeGranted: widget.onRecipientTypeGranted,
resourceDisplayName: widget.resourceDisplayName,
inviteConfig: widget.inviteConfig,
);
Expand Down
8 changes: 7 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: solidui
description: 'A UI library for building Solid applications with Flutter.'
version: 1.0.24
version: 1.0.26
homepage: https://github.com/anusii/solidui

# Scaffold a new Solid Pod file-browser app (a pod browser, with navigation
Expand Down Expand Up @@ -59,6 +59,12 @@ dev_dependencies:
sdk: flutter
window_manager: ^0.5.1

dependency_overrides:
solidpod:
git:
url: https://github.com/anusii/solidpod.git
ref: jess/694_revert_public

flutter:
uses-material-design: true
config:
Expand Down
Loading