Files
listenmeister/lib/pages/lists.dart
2025-10-29 20:57:37 +01:00

415 lines
12 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:listenmeister/main.dart';
import 'package:listenmeister/models/models.dart';
import 'package:listenmeister/pages/list_detail.dart';
import 'package:listenmeister/pages/layouts.dart';
import 'package:listenmeister/providers/theme.dart';
class ListsPage extends StatefulWidget {
const ListsPage({super.key});
@override
State<ListsPage> createState() => _ListsPageState();
}
class _ListsPageState extends State<ListsPage> {
late Future<List<Liste>> _listsFuture;
StreamSubscription? _listsSubscription;
Map<String, String> _layoutNameById = {};
bool _layoutsLoading = false;
@override
void initState() {
super.initState();
_loadLists();
_listsSubscription = apiService.watchLists().listen((_) {
_loadLists();
});
}
@override
void dispose() {
_listsSubscription?.cancel();
super.dispose();
}
void _loadLists() {
_listsFuture = apiService.getLists();
setState(() {});
_refreshLayoutCache();
}
Future<void> _refreshLayoutCache() async {
if (!mounted) return;
setState(() => _layoutsLoading = true);
try {
final layouts = await apiService.getStoreLayouts();
if (!mounted) return;
setState(() {
_layoutNameById = {
for (final layout in layouts) layout.id: layout.name,
};
_layoutsLoading = false;
});
} catch (_) {
if (!mounted) return;
setState(() => _layoutsLoading = false);
}
}
Future<void> _showListMenu(Liste list) async {
final String? result = await showModalBottomSheet<String>(
context: context,
builder: (c) => Wrap(
children: <Widget>[
if (list.owner == apiService.userId)
ListTile(
leading: const Icon(Icons.store_mall_directory_outlined),
title: const Text('Layout zuordnen'),
onTap: () => Navigator.pop(c, 'layout'),
),
if (list.owner == apiService.userId) const Divider(height: 0),
ListTile(
leading: const Icon(Icons.edit),
title: const Text('Umbenennen'),
onTap: () => Navigator.pop(c, 'edit'),
),
ListTile(
leading: const Icon(Icons.delete),
title: const Text('Löschen'),
onTap: () => Navigator.pop(c, 'delete'),
),
],
),
);
if (result == 'edit') {
_editList(list);
} else if (result == 'layout') {
_assignLayout(list);
} else if (result == 'delete') {
_deleteList(list);
}
}
Future<void> _assignLayout(Liste list) async {
if (list.owner != apiService.userId) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Nur Besitzer können Layouts zuordnen.'),
),
);
}
return;
}
List<StoreLayout> layouts = [];
try {
layouts = await apiService.getStoreLayouts();
if (!mounted) return;
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Layouts konnten nicht geladen werden: $e')),
);
return;
}
const String noLayoutOption = '__no_layout__';
final String? currentId = list.layoutId;
final String? selection = await showModalBottomSheet<String>(
context: context,
builder: (context) {
return SafeArea(
child: ListView(
shrinkWrap: true,
children: [
ListTile(
leading: const Icon(Icons.close),
title: const Text('Kein Layout zuordnen'),
trailing: currentId == null
? const Icon(Icons.check, size: 20)
: null,
onTap: () => Navigator.pop(context, noLayoutOption),
),
if (layouts.isNotEmpty) const Divider(height: 0),
if (layouts.isEmpty)
const Padding(
padding: EdgeInsets.all(16),
child: Text('Keine Layouts verfügbar.'),
),
...layouts.map((layout) {
return ListTile(
leading: Icon(
layout.owner == apiService.userId
? Icons.store_mall_directory
: Icons.share,
),
title: Text(layout.name),
subtitle: layout.address != null && layout.address!.isNotEmpty
? Text(layout.address!)
: null,
trailing: currentId == layout.id
? const Icon(Icons.check, size: 20)
: null,
onTap: () => Navigator.pop(context, layout.id),
);
}),
],
),
);
},
);
if (!mounted) return;
if (selection == null) return;
final String? newLayoutId = selection == noLayoutOption ? null : selection;
if (newLayoutId == currentId) return;
try {
await apiService.updateList(
list.id,
layoutId: newLayoutId,
clearLayout: newLayoutId == null,
);
list.layoutId = newLayoutId;
_loadLists();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Layout konnte nicht gespeichert werden: $e')),
);
}
}
}
Future<void> _editList(Liste list) async {
final TextEditingController titleCtl = TextEditingController(
text: list.title,
);
final bool? ok = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: const Text('Liste umbenennen'),
content: TextField(
controller: titleCtl,
decoration: const InputDecoration(labelText: 'Titel'),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(c, false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.pop(c, true),
child: const Text('Speichern'),
),
],
),
);
if (ok != true || titleCtl.text.trim().isEmpty) return;
try {
await apiService.updateList(list.id, title: titleCtl.text.trim());
_loadLists();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Fehler: $e')));
}
}
}
Future<void> _deleteList(Liste list) async {
final bool? ok = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: const Text('Liste löschen?'),
content: Text(
'Möchtest du die Liste "${list.title}" wirklich löschen?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(c, false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.pop(c, true),
child: const Text('Löschen'),
),
],
),
);
if (ok != true) return;
try {
await apiService.deleteList(list.id);
_loadLists();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Fehler: $e')));
}
}
}
Future<void> _createList() async {
final TextEditingController titleCtl = TextEditingController();
final bool? ok = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: const Text('Neue Liste'),
content: TextField(
controller: titleCtl,
decoration: const InputDecoration(labelText: 'Titel'),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(c, false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.pop(c, true),
child: const Text('Erstellen'),
),
],
),
);
if (ok != true || titleCtl.text.trim().isEmpty) return;
try {
await apiService.createList(titleCtl.text.trim());
_loadLists();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Fehler: $e')));
}
}
}
void _openList(Liste list) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => ListDetailPage(list: list)),
).then((_) => _loadLists());
}
@override
Widget build(BuildContext context) {
final ThemeProvider themeProvider = Provider.of<ThemeProvider>(
context,
listen: false,
);
return Scaffold(
appBar: AppBar(
title: const Text('Deine Listen'),
actions: [
IconButton(
tooltip: 'Ladenlayouts',
icon: const Icon(Icons.store_mall_directory_outlined),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const LayoutsPage()),
);
},
),
PopupMenuButton<ThemeMode>(
onSelected: (mode) => themeProvider.setThemeMode(mode),
itemBuilder: (context) => [
const PopupMenuItem(value: ThemeMode.light, child: Text('Hell')),
const PopupMenuItem(value: ThemeMode.dark, child: Text('Dunkel')),
const PopupMenuItem(
value: ThemeMode.system,
child: Text('System'),
),
],
icon: const Icon(Icons.brightness_6_outlined),
),
IconButton(
tooltip: 'Ausloggen',
icon: const Icon(Icons.logout),
onPressed: () {
apiService.logout();
},
),
],
),
body: FutureBuilder<List<Liste>>(
future: _listsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Fehler: ${snapshot.error}'));
}
final lists = snapshot.data ?? [];
if (lists.isEmpty) {
return const Center(child: Text('Keine Listen gefunden.'));
}
return ListView.builder(
itemCount: lists.length,
itemBuilder: (c, i) {
final l = lists[i];
final String? layoutName = l.layoutId != null
? _layoutNameById[l.layoutId!]
: null;
final String? subtitle = layoutName != null
? 'Layout: $layoutName'
: l.layoutId != null
? (_layoutsLoading
? 'Layout wird geladen...'
: 'Layout-ID: ${l.layoutId}')
: null;
Widget? trailing;
if (l.owner == apiService.userId) {
trailing = IconButton(
tooltip: l.layoutId != null
? 'Layout wechseln'
: 'Layout zuordnen',
icon: Icon(
l.layoutId != null
? Icons.store_mall_directory
: Icons.store_mall_directory_outlined,
),
onPressed: () => _assignLayout(l),
);
} else if (l.layoutId != null) {
trailing = const Icon(Icons.store_mall_directory_outlined);
}
return ListTile(
title: Text(l.title),
subtitle: subtitle != null ? Text(subtitle) : null,
onTap: () => _openList(l),
onLongPress: () => _showListMenu(l),
trailing: trailing,
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _createList,
child: const Icon(Icons.add),
),
);
}
}