231 lines
7.4 KiB
Dart
231 lines
7.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../data/share_service.dart';
|
|
import '../data/share.dart';
|
|
|
|
class ShareBottomSheet extends StatefulWidget {
|
|
const ShareBottomSheet({
|
|
super.key,
|
|
required this.igelId,
|
|
required this.shareService,
|
|
required this.accessRole, // AccessRole.owner/editor/viewer
|
|
required this.token,
|
|
});
|
|
|
|
final int igelId;
|
|
final ShareService shareService;
|
|
final String token;
|
|
final AccessRole accessRole;
|
|
|
|
@override
|
|
State<ShareBottomSheet> createState() => _ShareBottomSheetState();
|
|
}
|
|
|
|
class _ShareBottomSheetState extends State<ShareBottomSheet> {
|
|
final _emailCtrl = TextEditingController();
|
|
String _role = 'viewer';
|
|
Future<List<Share>>? _future;
|
|
final ScrollController _scrollCtrl = ScrollController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_future = widget.shareService
|
|
.listShares(igelId: widget.igelId, token: widget.token);
|
|
}
|
|
|
|
Future<void> _reload() async {
|
|
setState(() {
|
|
_future = widget.shareService
|
|
.listShares(igelId: widget.igelId, token: widget.token);
|
|
});
|
|
}
|
|
|
|
Future<void> _invite() async {
|
|
final email = _emailCtrl.text.trim();
|
|
if (email.isEmpty) return;
|
|
await widget.shareService.invite(
|
|
igelId: widget.igelId,
|
|
email: email,
|
|
role: _role,
|
|
token: widget.token,
|
|
);
|
|
_emailCtrl.clear();
|
|
await _reload();
|
|
}
|
|
|
|
Future<void> _updateRole(Share s, String newRole) async {
|
|
await widget.shareService
|
|
.updateRole(shareId: s.id, role: newRole, token: widget.token);
|
|
await _reload();
|
|
}
|
|
|
|
Future<void> _revoke(Share s) async {
|
|
await widget.shareService.revoke(shareId: s.id, token: widget.token);
|
|
await _reload();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollCtrl.dispose();
|
|
_emailCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final canInvite = widget.accessRole == AccessRole.owner;
|
|
|
|
return SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(children: [
|
|
const Icon(Icons.share),
|
|
const SizedBox(width: 8),
|
|
Text('Igel teilen',
|
|
style: Theme.of(context).textTheme.titleLarge),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
]),
|
|
const SizedBox(height: 12),
|
|
|
|
// Einladen (nur Owner)
|
|
if (canInvite)
|
|
_InviteRow(
|
|
emailCtrl: _emailCtrl,
|
|
role: _role,
|
|
onRoleChanged: (v) => setState(() => _role = v),
|
|
onInvite: _invite,
|
|
),
|
|
if (canInvite) const SizedBox(height: 16),
|
|
|
|
// Liste der Freigaben
|
|
Expanded(
|
|
child: FutureBuilder<List<Share>>(
|
|
future: _future,
|
|
builder: (context, snap) {
|
|
if (snap.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
if (snap.hasError) {
|
|
return Center(child: Text('Fehler: ${snap.error}'));
|
|
}
|
|
final items = snap.data ?? const [];
|
|
if (items.isEmpty) {
|
|
return const Center(child: Text('Keine Freigaben'));
|
|
}
|
|
return ScrollConfiguration(
|
|
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
|
child: Scrollbar(
|
|
controller: _scrollCtrl,
|
|
child: ListView.separated(
|
|
controller: _scrollCtrl,
|
|
primary: false,
|
|
itemCount: items.length,
|
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
|
itemBuilder: (ctx, i) {
|
|
final s = items[i];
|
|
|
|
// Priorität: pending -> invitedEmail, accepted -> targetEmail
|
|
final label =
|
|
(s.invitedEmail != null && s.invitedEmail!.isNotEmpty)
|
|
? s.invitedEmail!
|
|
: ((s.targetEmail != null &&
|
|
s.targetEmail!.isNotEmpty)
|
|
? s.targetEmail!
|
|
: 'Unbekannt');
|
|
|
|
return ListTile(
|
|
leading: Icon(
|
|
s.status == 'accepted'
|
|
? Icons.check_circle_outline
|
|
: Icons.hourglass_bottom,
|
|
),
|
|
title: Text(label),
|
|
subtitle:
|
|
Text('Rolle: ${s.role} · Status: ${s.status}'),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (canInvite && s.status == 'accepted')
|
|
PopupMenuButton<String>(
|
|
onSelected: (v) => _updateRole(s, v),
|
|
itemBuilder: (_) => const [
|
|
PopupMenuItem(
|
|
value: 'viewer', child: Text('Viewer')),
|
|
PopupMenuItem(
|
|
value: 'editor', child: Text('Editor')),
|
|
],
|
|
child: const Icon(Icons.admin_panel_settings),
|
|
),
|
|
if (canInvite)
|
|
IconButton(
|
|
icon: const Icon(Icons.remove_circle_outline),
|
|
tooltip: 'Zugriff entfernen',
|
|
onPressed: () => _revoke(s),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _InviteRow extends StatelessWidget {
|
|
const _InviteRow({
|
|
required this.emailCtrl,
|
|
required this.role,
|
|
required this.onRoleChanged,
|
|
required this.onInvite,
|
|
});
|
|
final TextEditingController emailCtrl;
|
|
final String role;
|
|
final ValueChanged<String> onRoleChanged;
|
|
final VoidCallback onInvite;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: emailCtrl,
|
|
decoration: const InputDecoration(labelText: 'E-Mail einladen'),
|
|
keyboardType: TextInputType.emailAddress,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
DropdownButton<String>(
|
|
value: role,
|
|
items: const [
|
|
DropdownMenuItem(value: 'viewer', child: Text('Viewer')),
|
|
DropdownMenuItem(value: 'editor', child: Text('Editor')),
|
|
],
|
|
onChanged: (v) {
|
|
if (v != null) onRoleChanged(v);
|
|
},
|
|
),
|
|
const SizedBox(width: 8),
|
|
ElevatedButton.icon(
|
|
onPressed: onInvite,
|
|
icon: const Icon(Icons.send),
|
|
label: const Text('Einladen'),
|
|
),
|
|
]);
|
|
}
|
|
}
|