blah
This commit is contained in:
43
lib/widgets/auth_gate.dart
Normal file
43
lib/widgets/auth_gate.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:shoppinglist/main.dart';
|
||||
import 'package:shoppinglist/pages/lists.dart';
|
||||
import 'package:shoppinglist/pages/login.dart';
|
||||
|
||||
class AuthGate extends StatefulWidget {
|
||||
const AuthGate({super.key});
|
||||
|
||||
@override
|
||||
State<AuthGate> createState() => _AuthGateState();
|
||||
}
|
||||
|
||||
class _AuthGateState extends State<AuthGate> {
|
||||
StreamSubscription? _authSub;
|
||||
bool _loggedIn = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loggedIn = pb.authStore.isValid;
|
||||
_authSub = pb.authStore.onChange.listen((_) {
|
||||
setState(() => _loggedIn = pb.authStore.isValid);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_authSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_loggedIn) {
|
||||
return const LoginPage();
|
||||
} else {
|
||||
return const ListsPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
264
lib/widgets/members_dialog.dart
Normal file
264
lib/widgets/members_dialog.dart
Normal file
@@ -0,0 +1,264 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pocketbase/pocketbase.dart';
|
||||
|
||||
import 'package:shoppinglist/main.dart';
|
||||
import 'package:shoppinglist/models/models.dart';
|
||||
|
||||
class MembersDialog extends StatefulWidget {
|
||||
final Liste list;
|
||||
const MembersDialog({super.key, required this.list});
|
||||
|
||||
@override
|
||||
State<MembersDialog> createState() => _MembersDialogState();
|
||||
}
|
||||
|
||||
class _MembersDialogState extends State<MembersDialog> {
|
||||
final TextEditingController _emailCtl = TextEditingController();
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isUsersLoading = true;
|
||||
final Map<String, String> _userEmails = {};
|
||||
bool _showAddMemberField = false;
|
||||
bool _hasChanges = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUsers();
|
||||
}
|
||||
|
||||
Future<void> _loadUsers() async {
|
||||
setState(() => _isUsersLoading = true);
|
||||
try {
|
||||
final List<String> ids = <String>{
|
||||
widget.list.owner,
|
||||
...widget.list.members,
|
||||
}.where((id) => id.isNotEmpty).toList();
|
||||
if (ids.isEmpty) {
|
||||
setState(() => _isUsersLoading = false);
|
||||
return;
|
||||
}
|
||||
final List<RecordModel> users = await apiService.getUsersByIds(ids);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
for (final RecordModel user in users) {
|
||||
_userEmails[user.id] = user.data['email'] as String;
|
||||
}
|
||||
_isUsersLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error = 'Fehler beim Laden der Benutzer.';
|
||||
_isUsersLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addMember() async {
|
||||
final String email = _emailCtl.text.trim();
|
||||
if (email.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final RecordModel? user = await apiService.findUserByEmail(email);
|
||||
if (user == null) {
|
||||
throw Exception('Benutzer nicht gefunden.');
|
||||
}
|
||||
if (widget.list.owner == user.id ||
|
||||
widget.list.members.contains(user.id)) {
|
||||
throw Exception('Benutzer ist bereits Mitglied.');
|
||||
}
|
||||
|
||||
final List<String> newMembers = [...widget.list.members, user.id];
|
||||
await apiService.updateList(widget.list.id, members: newMembers);
|
||||
|
||||
setState(() {
|
||||
widget.list.members = newMembers;
|
||||
_userEmails[user.id] = user.data['email'] as String;
|
||||
_emailCtl.clear();
|
||||
_hasChanges = true;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _error = e.toString());
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _removeMember(String memberId) async {
|
||||
final String? memberEmail = _userEmails[memberId];
|
||||
final bool? ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (c) => AlertDialog(
|
||||
title: const Text('Mitglied entfernen?'),
|
||||
content: Text(
|
||||
'Möchtest du "${memberEmail ?? memberId}" wirklich aus der Liste entfernen?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(c, false),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(c, true),
|
||||
child: const Text('Entfernen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (ok != true) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final List<String> newMembers = widget.list.members
|
||||
.where((id) => id != memberId)
|
||||
.toList();
|
||||
await apiService.updateList(widget.list.id, members: newMembers);
|
||||
|
||||
setState(() {
|
||||
widget.list.members = newMembers;
|
||||
_hasChanges = true;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _error = e.toString());
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isOwner = widget.list.owner == apiService.userId;
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Mitglieder'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: _isUsersLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.list.members.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
'Niemand',
|
||||
style: TextStyle(fontStyle: FontStyle.italic),
|
||||
),
|
||||
)
|
||||
else
|
||||
...widget.list.members.map(
|
||||
(id) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${_userEmails[id] ?? 'Unbekannt'}${_userEmails[widget.list.owner] == (_userEmails[id] ?? id) ? " (Besitzer)" : ""}",
|
||||
),
|
||||
),
|
||||
if (isOwner)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove_circle_outline),
|
||||
tooltip: 'Entfernen',
|
||||
onPressed:
|
||||
_isLoading ||
|
||||
_userEmails[widget.list.owner] ==
|
||||
(_userEmails[id] ?? id)
|
||||
? null
|
||||
: () => _removeMember(id),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isOwner) ...[
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
...[
|
||||
_showAddMemberField
|
||||
? Expanded(
|
||||
child: TextField(
|
||||
controller: _emailCtl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Email des Mitglieds',
|
||||
isDense: true,
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onSubmitted: (_) => _addMember(),
|
||||
autofocus: true,
|
||||
),
|
||||
)
|
||||
: Expanded(child: SizedBox.shrink()),
|
||||
],
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_showAddMemberField
|
||||
? Icons.close
|
||||
: Icons.add_circle_outline,
|
||||
),
|
||||
tooltip: _showAddMemberField
|
||||
? 'Schließen'
|
||||
: 'Mitglied hinzufügen',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showAddMemberField = !_showAddMemberField;
|
||||
_error = null;
|
||||
if (!_showAddMemberField) {
|
||||
_emailCtl.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (isOwner && _showAddMemberField)
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : _addMember,
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Hinzufügen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, _hasChanges),
|
||||
child: const Text('Schließen'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user