415 lines
12 KiB
Dart
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),
|
|
),
|
|
);
|
|
}
|
|
}
|