From a2649c6a2807fa1ee20fc203b2edba3d3e34466b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sat, 15 Apr 2023 11:30:37 +0200 Subject: [PATCH] Update Edit Event State Backend --- lib/src/provider/events.dart | 7 + .../event/components/loaded_event.dart | 234 ++---------------- .../event/components/tabs/edit_event.dart | 197 +++++++++++++++ .../components/{ => tabs}/fight_list.dart | 6 +- .../components/{ => tabs}/team_list.dart | 11 +- lib/src/screens/event/event.dart | 22 +- lib/src/screens/event/state/event.dart | 92 +++++++ 7 files changed, 341 insertions(+), 228 deletions(-) create mode 100644 lib/src/screens/event/components/tabs/edit_event.dart rename lib/src/screens/event/components/{ => tabs}/fight_list.dart (99%) rename lib/src/screens/event/components/{ => tabs}/team_list.dart (90%) create mode 100644 lib/src/screens/event/state/event.dart diff --git a/lib/src/provider/events.dart b/lib/src/provider/events.dart index ae14902..c17006d 100644 --- a/lib/src/provider/events.dart +++ b/lib/src/provider/events.dart @@ -31,3 +31,10 @@ final eventsListProvider = FutureProvider>((ref) async { final repo = await ref.watch(eventRepositoryProvider.future); return repo.listEvents(); }, dependencies: [eventRepositoryProvider]); + +final eventFutureProvider = + FutureProvider.family.autoDispose((ref, id) async { + return ref + .watch(eventRepositoryProvider.future) + .then((value) => value.getEvent(id)); +}); diff --git a/lib/src/screens/event/components/loaded_event.dart b/lib/src/screens/event/components/loaded_event.dart index ecf534a..3def5ff 100644 --- a/lib/src/screens/event/components/loaded_event.dart +++ b/lib/src/screens/event/components/loaded_event.dart @@ -26,57 +26,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:steamwar_multitool/src/components/components.dart'; import 'package:steamwar_multitool/src/components/date_time_editor.dart'; import 'package:steamwar_multitool/src/provider/provider.dart'; -import 'package:steamwar_multitool/src/screens/event/components/fight_list.dart'; -import 'package:steamwar_multitool/src/screens/event/components/team_list.dart'; +import 'package:steamwar_multitool/src/screens/event/components/tabs/edit_event.dart'; +import 'package:steamwar_multitool/src/screens/event/components/tabs/fight_list.dart'; +import 'package:steamwar_multitool/src/screens/event/components/tabs/team_list.dart'; import 'package:steamwar_multitool/src/screens/event/event.dart'; import 'package:steamwar_multitool/src/delegates/delegates.dart'; +import 'package:steamwar_multitool/src/screens/event/state/event.dart'; import 'package:steamwar_multitool/src/types/types.dart'; class LoadedEventScreen extends HookConsumerWidget { - const LoadedEventScreen({Key? key, required this.eventData}) - : super(key: key); - - final EventExtended eventData; + const LoadedEventScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { + final eventData = ref.watch(eventProvider); final event = eventData.event; - final nameController = useTextEditingController(text: event.name); - final deadlineState = useState(event.deadline); - final startDateState = useState(event.start); - final endDateState = useState(event.end); - final maxTeamMembersState = useState(event.maxTeamMembers); - final maxTeamMembersController = - useTextEditingController(text: event.maxTeamMembers.toString()); - final invalidMaxTeamMembers = useState(false); - final schematicTypeState = useState(null); - final publicOnlyState = useState(event.publicSchemsOnly); - final spectateSystemState = useState(event.spectateSystem); final tabController = useTabController(initialLength: 3); - useMemoized(() async { - schematicTypeState.value = await ref - .read(schematicTypesProvider.future) - .then((value) => catchToNull(() => - value.firstWhere((element) => element.db == event.schemType))); - }); - - final fightCount = useMemoized(() { - Map fightCount = {}; - for (var team in eventData.teams) { - fightCount[team.id] = 0; - } - - for (var fight in eventData.fights) { - fightCount[fight.blueTeam.id] = - (fightCount[fight.blueTeam.id] ?? 0) + 1; - fightCount[fight.redTeam.id] = (fightCount[fight.redTeam.id] ?? 0) + 1; - } - return fightCount; - }, [eventData.fights, eventData.teams]); - - final changed = useState(false); return Scaffold( appBar: AppBar( title: Text("${"Edit"} ${event.name}"), @@ -113,20 +80,20 @@ class LoadedEventScreen extends HookConsumerWidget { icon: const Icon(Icons.delete_outline, color: Colors.red), ), IconButton( - onPressed: changed.value + onPressed: ref.watch(changedProvider) ? () { ref .read(eventRepositoryProvider.future) .then((value) => value.updateEvent( event.id, - nameController.text, - deadlineState.value, - startDateState.value, - endDateState.value, - maxTeamMembersState.value, - schematicTypeState.value?.db, - publicOnlyState.value, - spectateSystemState.value, + ref.read(eventNameProvider), + ref.read(eventDeadlineProvider), + ref.read(eventStartProvider), + ref.read(eventEndProvider), + ref.read(maxTeamSizeProvider), + ref.read(schematicTypeProvider), + ref.read(publicOnlyProvider), + ref.read(spectateSystemProvider), )) .whenComplete( () => ref.invalidate(eventsListProvider)); @@ -138,7 +105,7 @@ class LoadedEventScreen extends HookConsumerWidget { leading: BackButton( onPressed: () async { var canPop = false; - if (changed.value) { + if (ref.read(changedProvider)) { final accepted = await showDialog( context: context, builder: (context) => AlertDialog( @@ -181,171 +148,10 @@ class LoadedEventScreen extends HookConsumerWidget { padding: const EdgeInsets.all(8.0), child: TabBarView( controller: tabController, - children: [ - ListView( - shrinkWrap: true, - children: [ - const SizedBox(height: 8), - TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: "Name", - border: OutlineInputBorder(), - ), - onChanged: (value) { - changed.value = true; - }, - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.spaceBetween, - children: [ - Column( - children: [ - const Tooltip( - message: "Deadline", - child: Icon(Icons.block), - ), - DateTimeEditor((p0) { - deadlineState.value = p0; - changed.value = true; - }, deadlineState.value, true), - ], - ), - Column( - children: [ - const Tooltip( - message: "Start", - child: Icon(Icons.swipe_right_alt), - ), - DateTimeEditor((p0) { - startDateState.value = p0; - changed.value = true; - }, startDateState.value, true), - ], - ), - Column( - children: [ - const Tooltip( - message: "End", - child: Icon(Icons.swipe_left_alt), - ), - DateTimeEditor((p0) { - endDateState.value = p0; - changed.value = true; - }, endDateState.value, true), - ], - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - SizedBox( - width: 150, - child: TextField( - controller: maxTeamMembersController, - decoration: InputDecoration( - labelText: "Max Team Members", - border: const OutlineInputBorder(), - errorText: invalidMaxTeamMembers.value - ? "Must be a number above 0" - : null), - keyboardType: TextInputType.number, - onChanged: (value) { - if (value.isEmpty || - int.tryParse(value) == null || - int.parse(value) <= 0) { - invalidMaxTeamMembers.value = true; - } else { - invalidMaxTeamMembers.value = false; - maxTeamMembersState.value = int.parse(value); - changed.value = true; - } - }, - ), - ), - Expanded( - child: Slider( - value: maxTeamMembersState.value.toDouble(), - onChanged: (p0) { - maxTeamMembersState.value = p0.toInt(); - maxTeamMembersController.text = - p0.toInt().toString(); - changed.value = true; - }, - min: min(1, maxTeamMembersState.value.toDouble()), - max: - max(30, maxTeamMembersState.value.toDouble()), - divisions: - max(30, maxTeamMembersState.value.toDouble()) - .toInt() - - 1, - label: maxTeamMembersState.value.toString(), - ), - ), - ], - ), - const SizedBox(height: 8), - ref.read(schematicTypesProvider).when( - data: (data) { - return DropdownMenu( - dropdownMenuEntries: [ - const DropdownMenuEntry(value: null, label: ""), - if (schematicTypeState.value != null && - !data.contains(schematicTypeState.value)) - DropdownMenuEntry( - value: schematicTypeState.value!, - label: schematicTypeState.value!.name, - ), - for (final type in data) - DropdownMenuEntry( - value: type, - label: type.name, - ) - ], - initialSelection: schematicTypeState.value, - enableSearch: true, - enableFilter: true, - width: 200, - menuHeight: 300, - label: const Text("Schematic Type"), - onSelected: (v) async { - schematicTypeState.value = v; - changed.value = true; - }, - ); - }, - error: (err, stk) { - return ErrorComponent(err, stk); - }, - loading: () { - return const Disabled(initialValue: "Schematic Type"); - }, - ), - const SizedBox(height: 8), - SwitchListTile( - value: publicOnlyState.value, - onChanged: (value) { - publicOnlyState.value = value; - changed.value = true; - }, - title: const Text("Public Only"), - ), - SwitchListTile( - value: spectateSystemState.value, - onChanged: (value) { - spectateSystemState.value = value; - changed.value = true; - }, - title: const Text("Use Spectate System"), - ), - ], - ), - TeamList(eventData.teams, fightCount), - EventFightList( - eventData: eventData, - ), + children: const [ + EditEventTab(), + TeamList(), + EventFightList(), ], ), ), diff --git a/lib/src/screens/event/components/tabs/edit_event.dart b/lib/src/screens/event/components/tabs/edit_event.dart new file mode 100644 index 0000000..7590d82 --- /dev/null +++ b/lib/src/screens/event/components/tabs/edit_event.dart @@ -0,0 +1,197 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:steamwar_multitool/src/components/components.dart'; +import 'package:steamwar_multitool/src/provider/data.dart'; +import 'package:steamwar_multitool/src/screens/event/event.dart'; +import 'package:steamwar_multitool/src/screens/event/state/event.dart'; +import 'package:steamwar_multitool/src/types/schematic.dart'; + +class EditEventTab extends HookConsumerWidget { + const EditEventTab({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final nameTextController = + useTextEditingController(text: ref.watch(eventNameProvider)); + + final maxTeamMembersController = useTextEditingController( + text: ref.watch(maxTeamSizeProvider).toString()); + final invalidMaxTeamMembers = useState(false); + + return ListView( + shrinkWrap: true, + children: [ + const SizedBox(height: 8), + TextField( + controller: nameTextController, + decoration: const InputDecoration( + labelText: "Name", + border: OutlineInputBorder(), + ), + onChanged: (value) { + ref.read(eventNameProvider.notifier).state = value; + }, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.spaceBetween, + children: [ + Column( + children: [ + const Tooltip( + message: "Deadline", + child: Icon(Icons.block), + ), + DateTimeEditor( + (p0) { + ref.read(eventDeadlineProvider.notifier).state = p0; + }, + ref.watch(eventDeadlineProvider), + true, + ), + ], + ), + Column( + children: [ + const Tooltip( + message: "Start", + child: Icon(Icons.swipe_right_alt), + ), + DateTimeEditor((p0) { + ref.read(eventStartProvider.notifier).state = p0; + }, ref.watch(eventStartProvider), true), + ], + ), + Column( + children: [ + const Tooltip( + message: "End", + child: Icon(Icons.swipe_left_alt), + ), + DateTimeEditor((p0) { + ref.read(eventEndProvider.notifier).state = p0; + }, ref.watch(eventEndProvider), true), + ], + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + SizedBox( + width: 150, + child: TextField( + controller: maxTeamMembersController, + decoration: InputDecoration( + labelText: "Max Team Members", + border: const OutlineInputBorder(), + errorText: invalidMaxTeamMembers.value + ? "Must be a number above 0" + : null), + keyboardType: TextInputType.number, + onChanged: (value) { + if (value.isEmpty || + int.tryParse(value) == null || + int.parse(value) <= 0) { + invalidMaxTeamMembers.value = true; + } else { + invalidMaxTeamMembers.value = false; + ref.read(maxTeamSizeProvider.notifier).state = + int.parse(value); + } + }, + ), + ), + Expanded( + child: Slider( + value: ref.read(maxTeamSizeProvider).toDouble(), + onChanged: (p0) { + ref.read(maxTeamSizeProvider.notifier).state = p0.toInt(); + maxTeamMembersController.text = p0.toInt().toString(); + }, + min: min(1, ref.read(maxTeamSizeProvider).toDouble()), + max: max(30, ref.read(maxTeamSizeProvider).toDouble()), + divisions: + max(30, ref.read(maxTeamSizeProvider).toDouble()).toInt() - + 1, + label: ref.read(maxTeamSizeProvider).toString(), + ), + ), + ], + ), + const SizedBox(height: 8), + ref.watch(schematicTypesProvider).when( + data: (data) { + final schematicTypeState = ref.watch(schematicTypeProvider); + + return DropdownMenu( + dropdownMenuEntries: [ + const DropdownMenuEntry(value: null, label: ""), + for (final type in data) + DropdownMenuEntry( + value: type, + label: type.name, + ) + ], + initialSelection: catchToNull(() => data + .firstWhere((element) => element.db == schematicTypeState)), + enableSearch: true, + enableFilter: true, + width: 200, + menuHeight: 300, + label: const Text("Schematic Type"), + onSelected: (v) async { + ref.read(schematicTypeProvider.notifier).state = v?.db; + }, + ); + }, + error: (err, stk) { + return ErrorComponent(err, stk); + }, + loading: () { + return const Disabled(initialValue: "Schematic Type"); + }, + ), + const SizedBox(height: 8), + SwitchListTile( + value: ref.watch(publicOnlyProvider), + onChanged: (value) { + ref.read(publicOnlyProvider.notifier).state = value; + }, + title: const Text("Public Only"), + ), + SwitchListTile( + value: ref.watch(spectateSystemProvider), + onChanged: (value) { + ref.read(spectateSystemProvider.notifier).state = value; + }, + title: const Text("Use Spectate System"), + ), + ], + ); + } +} diff --git a/lib/src/screens/event/components/fight_list.dart b/lib/src/screens/event/components/tabs/fight_list.dart similarity index 99% rename from lib/src/screens/event/components/fight_list.dart rename to lib/src/screens/event/components/tabs/fight_list.dart index eeb2e49..d6a3a88 100644 --- a/lib/src/screens/event/components/fight_list.dart +++ b/lib/src/screens/event/components/tabs/fight_list.dart @@ -29,6 +29,7 @@ import 'package:steamwar_multitool/src/components/date_time_editor.dart'; import 'package:steamwar_multitool/src/provider/provider.dart'; import 'package:steamwar_multitool/src/delegates/delegates.dart'; import 'package:steamwar_multitool/src/screens/event/components/dialogs/dialogs.dart'; +import 'package:steamwar_multitool/src/screens/event/state/event.dart'; import 'package:steamwar_multitool/src/screens/generator/bracket_generator.dart'; import 'package:steamwar_multitool/src/screens/generator/generators/group_generator.dart'; import 'package:steamwar_multitool/src/types/types.dart'; @@ -36,12 +37,11 @@ import 'package:steamwar_multitool/src/util/constants.dart'; import 'package:url_launcher/url_launcher.dart'; class EventFightList extends HookConsumerWidget { - final EventExtended eventData; - - const EventFightList({Key? key, required this.eventData}) : super(key: key); + const EventFightList({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { + final eventData = ref.watch(eventProvider); final event = eventData.event; final fights = useState>(eventData.fights); final selected = useState>([]); diff --git a/lib/src/screens/event/components/team_list.dart b/lib/src/screens/event/components/tabs/team_list.dart similarity index 90% rename from lib/src/screens/event/components/team_list.dart rename to lib/src/screens/event/components/tabs/team_list.dart index 88f4e6f..fb9abca 100644 --- a/lib/src/screens/event/components/team_list.dart +++ b/lib/src/screens/event/components/tabs/team_list.dart @@ -19,20 +19,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:steamwar_multitool/src/screens/event/state/event.dart'; import 'package:steamwar_multitool/src/types/types.dart'; class TeamList extends HookConsumerWidget { - final List teams; - final Map fightCount; - - const TeamList( - this.teams, - this.fightCount, { + const TeamList({ Key? key, }) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { + final teams = ref.watch(eventProvider).teams; + final fightCount = ref.watch(eventFightsProvider); + return ListView( children: [ ListTile( diff --git a/lib/src/screens/event/event.dart b/lib/src/screens/event/event.dart index d1b5e84..3ccd2de 100644 --- a/lib/src/screens/event/event.dart +++ b/lib/src/screens/event/event.dart @@ -22,6 +22,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:steamwar_multitool/src/provider/events.dart'; import 'package:steamwar_multitool/src/screens/event/components/loaded_event.dart'; +import 'package:steamwar_multitool/src/screens/event/state/event.dart'; import 'package:steamwar_multitool/src/types/types.dart'; T? catchToNull(T Function() f) { @@ -38,9 +39,7 @@ class EditEventScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final eventFuture = useMemoized(() => ref - .watch(eventRepositoryProvider.future) - .then((value) => value.getEvent(eventId))); + final eventFuture = ref.watch(eventFutureProvider(eventId).future); return FutureBuilder( future: eventFuture, @@ -65,8 +64,21 @@ class EditEventScreen extends HookConsumerWidget { } final eventData = snapshot.data as EventExtended; - return LoadedEventScreen( - eventData: eventData, + return ProviderScope( + overrides: [ + eventProvider.overrideWithValue(eventData), + eventNameProvider, + eventDeadlineProvider, + eventStartProvider, + eventEndProvider, + maxTeamSizeProvider, + schematicTypeProvider, + publicOnlyProvider, + spectateSystemProvider, + changedProvider, + eventFightsProvider, + ], + child: const LoadedEventScreen(), ); }, ); diff --git a/lib/src/screens/event/state/event.dart b/lib/src/screens/event/state/event.dart new file mode 100644 index 0000000..b99f7e3 --- /dev/null +++ b/lib/src/screens/event/state/event.dart @@ -0,0 +1,92 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:steamwar_multitool/src/provider/provider.dart'; +import 'package:steamwar_multitool/src/screens/event/event.dart'; +import 'package:steamwar_multitool/src/types/types.dart'; + +final eventProvider = Provider((ref) { + throw UnimplementedError(); +}); + +final eventNameProvider = StateProvider((ref) { + return ref.read(eventProvider).event.name; +}); + +final eventDeadlineProvider = StateProvider((ref) { + return ref.read(eventProvider).event.deadline; +}); + +final eventStartProvider = StateProvider((ref) { + return ref.read(eventProvider).event.start; +}); + +final eventEndProvider = StateProvider((ref) { + return ref.read(eventProvider).event.end; +}); + +final maxTeamSizeProvider = StateProvider((ref) { + return ref.read(eventProvider).event.maxTeamMembers; +}); + +final schematicTypeProvider = StateProvider((ref) { + return ref.read(eventProvider).event.schemType; +}); + +final schematicTypeFutureProvider = FutureProvider((ref) async { + final types = await ref.read(schematicTypesProvider.future); + final type = catchToNull(() => types + .firstWhere((element) => element.db == ref.watch(schematicTypeProvider))); + return type; +}); + +final publicOnlyProvider = StateProvider((ref) { + return ref.read(eventProvider).event.publicSchemsOnly; +}); + +final spectateSystemProvider = StateProvider((ref) { + return ref.read(eventProvider).event.spectateSystem; +}); + +final changedProvider = Provider((ref) { + final event = ref.watch(eventProvider); + return event.event.name != ref.watch(eventNameProvider) || + event.event.deadline != ref.watch(eventDeadlineProvider) || + event.event.start != ref.watch(eventStartProvider) || + event.event.end != ref.watch(eventEndProvider) || + event.event.maxTeamMembers != ref.watch(maxTeamSizeProvider) || + event.event.schemType != ref.watch(schematicTypeProvider) || + event.event.publicSchemsOnly != ref.watch(publicOnlyProvider) || + event.event.spectateSystem != ref.watch(spectateSystemProvider); +}); + +final eventFightsProvider = Provider>((ref) { + final eventData = ref.watch(eventProvider); + Map fightCount = {}; + for (var team in eventData.teams) { + fightCount[team.id] = 0; + } + + for (var fight in eventData.fights) { + fightCount[fight.blueTeam.id] = (fightCount[fight.blueTeam.id] ?? 0) + 1; + fightCount[fight.redTeam.id] = (fightCount[fight.redTeam.id] ?? 0) + 1; + } + return fightCount; +});