mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-06 03:31:02 -06:00
261 lines
11 KiB
Dart
261 lines
11 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:drift/drift.dart' show TableOrViewStatements;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:phylum/libphylum/actions/action_resource_share.dart';
|
|
import 'package:phylum/libphylum/actions/action_user_invite.dart';
|
|
import 'package:phylum/libphylum/db/resource_helpers.dart';
|
|
import 'package:phylum/libphylum/phylum_account.dart';
|
|
import 'package:phylum/util/dialogs.dart';
|
|
import 'package:phylum/util/permissions.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class ResourcePermissionsView extends StatefulWidget {
|
|
final String resourceId;
|
|
|
|
ResourcePermissionsView({required this.resourceId}) : super(key: ValueKey(resourceId));
|
|
|
|
@override
|
|
State<ResourcePermissionsView> createState() => _ResourcePermissionsViewState();
|
|
}
|
|
|
|
class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
|
|
String? resourceName;
|
|
Map<int, Permission>? permissions;
|
|
Map<int, Permission>? grants;
|
|
Permission? userPermission;
|
|
|
|
StreamSubscription? sub;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final account = context.read<PhylumAccount>();
|
|
sub = account.db.watchPermissions(widget.resourceId).listen((data) {
|
|
setState(() {
|
|
resourceName = data?.name;
|
|
userPermission = data?.userPermission;
|
|
permissions = data?.inheritedPermissions.parsePermissionMap();
|
|
grants = data?.grants.parsePermissionMap();
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
sub?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final grants = this.grants;
|
|
final permissions = this.permissions;
|
|
if (grants == null || permissions == null) return const CircularProgressIndicator();
|
|
|
|
final account = context.read<PhylumAccount>();
|
|
final repository = account.userRepository;
|
|
final grantEntries = grants.entries.toList(growable: false)
|
|
..sort(((a, b) {
|
|
return a.value == b.value ? a.key.compareTo(b.key) : a.value.compareTo(b.value);
|
|
}));
|
|
final permissionEntries = permissions.entries.toList(growable: false)
|
|
..sort(((a, b) {
|
|
return a.value == b.value ? a.key.compareTo(b.key) : a.value.compareTo(b.value);
|
|
}));
|
|
return DropdownButtonHideUnderline(
|
|
child: Column(
|
|
children: [
|
|
if (userPermission! & permissionShare != 0)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8.0),
|
|
child: ElevatedButton.icon(
|
|
label: Text('Add User'),
|
|
icon: Icon(Icons.person_add),
|
|
onPressed: () async {
|
|
final users = account.userRepository.users.entries
|
|
.where((e) => !grants.containsKey(e.key))
|
|
.map((e) => e.value)
|
|
.toList(growable: false)
|
|
..sort((a, b) {
|
|
final aName = a.displayName.isNotEmpty ? a.displayName : a.email;
|
|
final bName = b.displayName.isNotEmpty ? b.displayName : b.email;
|
|
return aName.compareTo(bName);
|
|
});
|
|
final user = await showOptionsDialogBuilder(
|
|
context,
|
|
users,
|
|
(user) => ListTile(title: Text(repository.getUserDisplayName(user.id))),
|
|
buildLastItem: (context) => ListTile(
|
|
leading: Icon(Icons.person_add),
|
|
title: Text('Invite'),
|
|
onTap: () async {
|
|
String email = '';
|
|
String name = '';
|
|
bool confirm = await showDialog(
|
|
// ignore: use_build_context_synchronously
|
|
context: context,
|
|
barrierDismissible: true,
|
|
builder: (context) => StatefulBuilder(builder: (context, setState) {
|
|
return AlertDialog(
|
|
title: const Text('Invite User'),
|
|
content: SizedBox(
|
|
width: 360,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
spacing: 12.0,
|
|
children: [
|
|
TextField(
|
|
decoration: InputDecoration(
|
|
labelText: 'Email',
|
|
hintText: 'djarin@example.com',
|
|
),
|
|
autofocus: true,
|
|
textCapitalization: TextCapitalization.none,
|
|
keyboardType: TextInputType.emailAddress,
|
|
textInputAction: TextInputAction.next,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
email = value;
|
|
});
|
|
},
|
|
),
|
|
TextField(
|
|
decoration: InputDecoration(
|
|
labelText: 'Name (optional)',
|
|
hintText: 'Din Djarin',
|
|
),
|
|
textCapitalization: TextCapitalization.words,
|
|
keyboardType: TextInputType.name,
|
|
textInputAction: TextInputAction.go,
|
|
onChanged: (value) {
|
|
name = value;
|
|
},
|
|
onSubmitted:
|
|
email.isEmpty ? null : (value) => Navigator.of(context).pop(true),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
child: Text(
|
|
'Cancel',
|
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
|
),
|
|
onPressed: () {
|
|
Navigator.of(context).pop(false);
|
|
},
|
|
),
|
|
ElevatedButton(
|
|
onPressed: email.isEmpty ? null : () => Navigator.of(context).pop(true),
|
|
child: const Text('Invite'),
|
|
)
|
|
],
|
|
);
|
|
}),
|
|
) ??
|
|
false;
|
|
|
|
if (!confirm || !context.mounted) return;
|
|
account.addAction(UserInviteAction(email: email, displayName: name));
|
|
},
|
|
),
|
|
filterList: (u, q) {
|
|
final query = q.toLowerCase();
|
|
return u
|
|
.where((u) => u.email.contains(query) || u.displayName.contains(query))
|
|
.toList(growable: false);
|
|
},
|
|
);
|
|
if (user == null || !context.mounted) return;
|
|
final permission = await showOptionsDialogBuilder(
|
|
context,
|
|
[
|
|
permissionSetReadOnly,
|
|
permissionSetReadWrite,
|
|
permissionSetReadWriteShare,
|
|
],
|
|
(p) => ListTile(leading: Icon(p.icon), title: Text(p.text)),
|
|
);
|
|
if (permission == null || !context.mounted) return;
|
|
account.addAction(ResourceShareAction(
|
|
resourceId: widget.resourceId,
|
|
resourceName: resourceName!,
|
|
user: user,
|
|
permission: permission,
|
|
));
|
|
},
|
|
),
|
|
),
|
|
for (final e in grantEntries)
|
|
ListTile(
|
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
|
leading: Icon(e.value.icon),
|
|
title: Text(repository.getUserDisplayName(e.key)),
|
|
subtitle: Text(e.value.text),
|
|
onTap: (userPermission! & permissionShare) == 0
|
|
? null
|
|
: () async {
|
|
final permission = await showOptionsDialogBuilder(
|
|
context,
|
|
[
|
|
permissionSetNone,
|
|
permissionSetReadOnly,
|
|
permissionSetReadWrite,
|
|
permissionSetReadWriteShare,
|
|
],
|
|
(p) => ListTile(leading: Icon(p.icon), title: Text(p.text)),
|
|
);
|
|
if (permission == null || permission == e.value) return;
|
|
final user =
|
|
await (account.db.users.select()..where((u) => u.id.equals(e.key))).getSingleOrNull();
|
|
if (user == null) return;
|
|
|
|
account.addAction(ResourceShareAction(
|
|
resourceId: widget.resourceId,
|
|
resourceName: resourceName!,
|
|
user: user,
|
|
permission: permission,
|
|
));
|
|
},
|
|
),
|
|
ListTile(
|
|
visualDensity: VisualDensity.compact,
|
|
dense: true,
|
|
title: const Text(
|
|
key: ValueKey('inherited'), 'Inherited Permissions', style: TextStyle(fontWeight: FontWeight.bold)),
|
|
),
|
|
for (final e in permissionEntries)
|
|
ListTile(
|
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
|
leading: Icon(e.value.icon),
|
|
title: Text(repository.getUserDisplayName(e.key)),
|
|
subtitle: Text(e.value.text),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
extension on Permission {
|
|
IconData get icon {
|
|
if (this == 0) return Icons.block;
|
|
if (this == -1) return Icons.shield_outlined;
|
|
if (this == permissionSetReadOnly) return Icons.visibility;
|
|
if (this == permissionSetReadWrite) return Icons.edit;
|
|
if (this == permissionSetReadWriteShare) return Icons.person_add;
|
|
return Icons.settings;
|
|
}
|
|
|
|
String get text {
|
|
if (this == 0) return 'None';
|
|
if (this == -1) return 'Admin';
|
|
if (this == permissionSetReadOnly) return 'Viewer';
|
|
if (this == permissionSetReadWrite) return 'Collaborator';
|
|
if (this == permissionSetReadWriteShare) return 'Editor';
|
|
return 'Custom';
|
|
}
|
|
}
|