first draft

This commit is contained in:
2025-10-29 17:20:44 +01:00
parent 0ce1d34eab
commit ed1ffc8be4
5 changed files with 1059 additions and 0 deletions

224
lib/pages/layouts.dart Normal file
View File

@@ -0,0 +1,224 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:listenmeister/main.dart';
import 'package:listenmeister/models/models.dart';
import 'package:listenmeister/pages/layout_detail.dart';
class LayoutsPage extends StatefulWidget {
const LayoutsPage({super.key});
@override
State<LayoutsPage> createState() => _LayoutsPageState();
}
class _LayoutsPageState extends State<LayoutsPage> {
late Future<List<StoreLayout>> _layoutsFuture;
StreamSubscription? _layoutsSubscription;
@override
void initState() {
super.initState();
_loadLayouts();
_layoutsSubscription = apiService.watchStoreLayouts().listen((_) {
_loadLayouts();
});
}
@override
void dispose() {
_layoutsSubscription?.cancel();
super.dispose();
}
void _loadLayouts() {
_layoutsFuture = apiService.getStoreLayouts();
setState(() {});
}
Future<void> _createLayout() async {
final TextEditingController nameCtl = TextEditingController();
final TextEditingController addressCtl = TextEditingController();
final TextEditingController latCtl = TextEditingController();
final TextEditingController lonCtl = TextEditingController();
bool isPublic = false;
final bool? ok = await showDialog<bool>(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
title: const Text('Neues Ladenlayout'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameCtl,
decoration: const InputDecoration(labelText: 'Name'),
autofocus: true,
),
TextField(
controller: addressCtl,
decoration: const InputDecoration(
labelText: 'Adresse (optional)',
),
),
TextField(
controller: latCtl,
decoration: const InputDecoration(
labelText: 'Breitengrad (optional)',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
),
TextField(
controller: lonCtl,
decoration: const InputDecoration(
labelText: 'Längengrad (optional)',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
),
SwitchListTile.adaptive(
value: isPublic,
onChanged: (value) {
setDialogState(() => isPublic = value);
},
title: const Text('Für andere sichtbar'),
contentPadding: EdgeInsets.zero,
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Erstellen'),
),
],
);
},
);
},
);
if (ok != true) return;
final String name = nameCtl.text.trim();
if (name.isEmpty) return;
double? latitude;
double? longitude;
final String latText = latCtl.text.trim();
final String lonText = lonCtl.text.trim();
if (latText.isNotEmpty) {
latitude = double.tryParse(latText.replaceAll(',', '.'));
if (latitude == null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ungültiger Breitengrad.')),
);
}
return;
}
}
if (lonText.isNotEmpty) {
longitude = double.tryParse(lonText.replaceAll(',', '.'));
if (longitude == null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ungültiger Längengrad.')),
);
}
return;
}
}
try {
await apiService.createStoreLayout(
name: name,
latitude: latitude,
longitude: longitude,
address: addressCtl.text.trim().isEmpty ? null : addressCtl.text.trim(),
isPublic: isPublic,
);
_loadLayouts();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Fehler: $e')));
}
}
}
Future<void> _openLayout(StoreLayout layout) async {
await Navigator.of(context).push<StoreLayout>(
MaterialPageRoute(builder: (_) => LayoutDetailPage(layout: layout)),
);
if (mounted) {
_loadLayouts();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ladenlayouts')),
body: FutureBuilder<List<StoreLayout>>(
future: _layoutsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Fehler: ${snapshot.error}'));
}
final layouts = snapshot.data ?? [];
if (layouts.isEmpty) {
return const Center(child: Text('Noch keine Layouts vorhanden.'));
}
return ListView.builder(
itemCount: layouts.length,
itemBuilder: (context, index) {
final layout = layouts[index];
return ListTile(
title: Text(layout.name),
subtitle: layout.address != null && layout.address!.isNotEmpty
? Text(layout.address!)
: null,
leading: Icon(
layout.owner == apiService.userId
? Icons.store_mall_directory
: Icons.share,
),
trailing: layout.isPublic
? const Icon(Icons.public, size: 20)
: null,
onTap: () => _openLayout(layout),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _createLayout,
child: const Icon(Icons.add),
),
);
}
}