Add Group Frontend
Dieser Commit ist enthalten in:
Ursprung
cdca1c8562
Commit
3cfdbbcdbd
@ -17,6 +17,12 @@ final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
|
|||||||
return repo.listEvents();
|
return repo.listEvents();
|
||||||
}, dependencies: [eventRepositoryProvider]);
|
}, dependencies: [eventRepositoryProvider]);
|
||||||
|
|
||||||
|
final groupsProvider = FutureProvider.autoDispose<List<String>>((ref) async {
|
||||||
|
return (await ref.watch(httpClient.future)).get("/data/groups").then((value) {
|
||||||
|
return (value.data as List).map((e) => e as String).toList();
|
||||||
|
});
|
||||||
|
}, dependencies: [httpClient]);
|
||||||
|
|
||||||
class EventRepository {
|
class EventRepository {
|
||||||
final Dio _client;
|
final Dio _client;
|
||||||
|
|
||||||
@ -71,15 +77,8 @@ class EventRepository {
|
|||||||
await _client.delete("/fights/$id");
|
await _client.delete("/fights/$id");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createFight(
|
Future<void> createFight(int eventId, DateTime start, String mode, String map,
|
||||||
int eventId,
|
Team blue, Team red, int referee, String? group) {
|
||||||
DateTime start,
|
|
||||||
String mode,
|
|
||||||
String map,
|
|
||||||
Team blue,
|
|
||||||
Team red,
|
|
||||||
int referee,
|
|
||||||
) {
|
|
||||||
return _client.post("/fights", data: {
|
return _client.post("/fights", data: {
|
||||||
"event": eventId,
|
"event": eventId,
|
||||||
"spielmodus": mode,
|
"spielmodus": mode,
|
||||||
@ -88,6 +87,7 @@ class EventRepository {
|
|||||||
"blueTeam": blue.id,
|
"blueTeam": blue.id,
|
||||||
"redTeam": red.id,
|
"redTeam": red.id,
|
||||||
"kampfleiter": referee,
|
"kampfleiter": referee,
|
||||||
|
"group": group ?? "null",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ class EventRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateFight(int id, DateTime start, String mode, String map,
|
Future<void> updateFight(int id, DateTime start, String mode, String map,
|
||||||
int referee, Team blue, Team red) {
|
int referee, Team blue, Team red, String? group) {
|
||||||
return _client.put("/fights/$id", data: {
|
return _client.put("/fights/$id", data: {
|
||||||
"spielmodus": mode,
|
"spielmodus": mode,
|
||||||
"map": map,
|
"map": map,
|
||||||
@ -105,6 +105,7 @@ class EventRepository {
|
|||||||
"kampfleiter": referee,
|
"kampfleiter": referee,
|
||||||
"blueTeam": blue.id,
|
"blueTeam": blue.id,
|
||||||
"redTeam": red.id,
|
"redTeam": red.id,
|
||||||
|
"group": group ?? "null",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,9 +119,10 @@ class EventFight {
|
|||||||
final Team blueTeam;
|
final Team blueTeam;
|
||||||
final User kampfleiter;
|
final User kampfleiter;
|
||||||
final int score;
|
final int score;
|
||||||
|
final String? group;
|
||||||
|
|
||||||
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
|
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
|
||||||
this.blueTeam, this.kampfleiter, this.score);
|
this.blueTeam, this.kampfleiter, this.score, this.group);
|
||||||
|
|
||||||
Team get winner {
|
Team get winner {
|
||||||
return getTeamWithContextColor(Colors.white);
|
return getTeamWithContextColor(Colors.white);
|
||||||
@ -150,7 +152,8 @@ class EventFight {
|
|||||||
Team.fromJson(json["redTeam"]),
|
Team.fromJson(json["redTeam"]),
|
||||||
Team.fromJson(json["blueTeam"]),
|
Team.fromJson(json["blueTeam"]),
|
||||||
User.fromJson(json["kampfleiter"]),
|
User.fromJson(json["kampfleiter"]),
|
||||||
json["ergebnis"]);
|
json["ergebnis"],
|
||||||
|
json["group"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:math';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:grouped_list/grouped_list.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:steamwar_multitool/src/util/event_fight_exporter.dart';
|
import 'package:steamwar_multitool/src/util/event_fight_exporter.dart';
|
||||||
@ -414,7 +415,7 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
[date.value, lowest]);
|
[date.value, lowest]);
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text("Reshedule Fights"),
|
title: const Text("Reschedule Fights"),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -448,14 +449,15 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
final repo = await ref
|
final repo = await ref
|
||||||
.read(eventRepositoryProvider.future);
|
.read(eventRepositoryProvider.future);
|
||||||
for (final fight in selectedFights) {
|
for (final fight in selectedFights) {
|
||||||
repo.updateFight(
|
await repo.updateFight(
|
||||||
fight.id,
|
fight.id,
|
||||||
fight.start.add(offset),
|
fight.start.add(offset),
|
||||||
fight.gameMode,
|
fight.gameMode,
|
||||||
fight.map,
|
fight.map,
|
||||||
fight.kampfleiter.id,
|
fight.kampfleiter.id,
|
||||||
fight.blueTeam,
|
fight.blueTeam,
|
||||||
fight.redTeam);
|
fight.redTeam,
|
||||||
|
fight.group);
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@ -490,14 +492,15 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
await ref.read(eventRepositoryProvider.future);
|
await ref.read(eventRepositoryProvider.future);
|
||||||
for (final fight in fights.value) {
|
for (final fight in fights.value) {
|
||||||
if (selected.value.contains(fight.id)) {
|
if (selected.value.contains(fight.id)) {
|
||||||
repo.updateFight(
|
await repo.updateFight(
|
||||||
fight.id,
|
fight.id,
|
||||||
fight.start,
|
fight.start,
|
||||||
fight.gameMode,
|
fight.gameMode,
|
||||||
fight.map,
|
fight.map,
|
||||||
kampfleiter.id,
|
kampfleiter.id,
|
||||||
fight.blueTeam,
|
fight.blueTeam,
|
||||||
fight.redTeam);
|
fight.redTeam,
|
||||||
|
fight.group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
@ -505,6 +508,42 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
icon: const Icon(Icons.person)),
|
icon: const Icon(Icons.person)),
|
||||||
),
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: "Change Group",
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final group = await showSearch(
|
||||||
|
context: context,
|
||||||
|
delegate: GroupSearchDelegate(
|
||||||
|
await ref.read(groupsProvider.future)));
|
||||||
|
|
||||||
|
if (group == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
final repo =
|
||||||
|
await ref.read(eventRepositoryProvider.future);
|
||||||
|
for (final fight in fights.value) {
|
||||||
|
if (selected.value.contains(fight.id)) {
|
||||||
|
await repo.updateFight(
|
||||||
|
fight.id,
|
||||||
|
fight.start,
|
||||||
|
fight.gameMode,
|
||||||
|
fight.map,
|
||||||
|
fight.kampfleiter.id,
|
||||||
|
fight.blueTeam,
|
||||||
|
fight.redTeam,
|
||||||
|
group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.group)),
|
||||||
|
),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: "Delete Fights",
|
message: "Delete Fights",
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -531,7 +570,7 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
final repo = await ref
|
final repo = await ref
|
||||||
.read(eventRepositoryProvider.future);
|
.read(eventRepositoryProvider.future);
|
||||||
for (final fight in selected.value) {
|
for (final fight in selected.value) {
|
||||||
repo.deleteFight(fight);
|
await repo.deleteFight(fight);
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@ -579,7 +618,8 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
openExportedFights(fights.value, event.name);
|
openExportedFights(fights.value, event.name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@ -588,8 +628,51 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
const Center(
|
const Center(
|
||||||
child: Text("No fights yet"),
|
child: Text("No fights yet"),
|
||||||
),
|
),
|
||||||
for (final fight in fights.value)
|
GroupedListView(
|
||||||
ListTile(
|
elements: fights.value,
|
||||||
|
groupBy: (fight) => fight.group ?? "Ungrouped",
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemComparator: (fight1, fight2) =>
|
||||||
|
fight1.start.compareTo(fight2.start),
|
||||||
|
groupComparator: (group1, group2) => group1.compareTo(group2),
|
||||||
|
floatingHeader: true,
|
||||||
|
groupSeparatorBuilder: (group) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
final g = group == "Ungrouped" ? null : group;
|
||||||
|
final isAllSelected = selected.value
|
||||||
|
.where((element) =>
|
||||||
|
fights.value
|
||||||
|
.firstWhere((element2) => element2.id == element)
|
||||||
|
.group ==
|
||||||
|
g)
|
||||||
|
.length ==
|
||||||
|
fights.value.where((element) => element.group == g).length;
|
||||||
|
if (isAllSelected) {
|
||||||
|
selected.value = selected.value
|
||||||
|
.where((element) =>
|
||||||
|
fights.value
|
||||||
|
.firstWhere((element2) => element2.id == element)
|
||||||
|
.group !=
|
||||||
|
g)
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
selected.value = [
|
||||||
|
...selected.value,
|
||||||
|
...fights.value
|
||||||
|
.where((element) => element.group == g)
|
||||||
|
.map((e) => e.id)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
group,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, fight) => ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
"${fight.blueTeam.name} vs ${fight.redTeam.name}",
|
"${fight.blueTeam.name} vs ${fight.redTeam.name}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -639,6 +722,7 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -665,6 +749,7 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
final mode = useState(fight.gameMode);
|
final mode = useState(fight.gameMode);
|
||||||
final map = useState(fight.map);
|
final map = useState(fight.map);
|
||||||
final referrer = useState(fight.kampfleiter);
|
final referrer = useState(fight.kampfleiter);
|
||||||
|
final group = useState(fight.group ?? "");
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text("${fight.blueTeam.name} vs ${fight.redTeam.name}"),
|
title: Text("${fight.blueTeam.name} vs ${fight.redTeam.name}"),
|
||||||
@ -741,6 +826,24 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
? "None"
|
? "None"
|
||||||
: "${referrer.value.name} (${referrer.value!.id})"),
|
: "${referrer.value.name} (${referrer.value!.id})"),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text("Group"),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final selGroup = await showSearch(
|
||||||
|
context: context,
|
||||||
|
delegate: GroupSearchDelegate(
|
||||||
|
await ref.read(groupsProvider.future)));
|
||||||
|
if (selGroup != null) {
|
||||||
|
if (selGroup.isEmpty) {
|
||||||
|
group.value = "";
|
||||||
|
} else {
|
||||||
|
group.value = selGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(group.value.isEmpty ? "None" : group.value)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@ -784,8 +887,15 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var nav = Navigator.of(context);
|
var nav = Navigator.of(context);
|
||||||
final repo = await ref.read(eventRepositoryProvider.future);
|
final repo = await ref.read(eventRepositoryProvider.future);
|
||||||
await repo.updateFight(fight.id, start.value, mode.value,
|
await repo.updateFight(
|
||||||
map.value, referrer.value.id, blueTeam.value, redTeam.value);
|
fight.id,
|
||||||
|
start.value,
|
||||||
|
mode.value,
|
||||||
|
map.value,
|
||||||
|
referrer.value.id,
|
||||||
|
blueTeam.value,
|
||||||
|
redTeam.value,
|
||||||
|
group.value.isEmpty ? null : group.value);
|
||||||
fightsRefresher();
|
fightsRefresher();
|
||||||
nav.pop();
|
nav.pop();
|
||||||
},
|
},
|
||||||
@ -813,6 +923,7 @@ class _AddFightWidget extends HookConsumerWidget {
|
|||||||
final redTeam = useState<Team?>(null);
|
final redTeam = useState<Team?>(null);
|
||||||
final date = useState<DateTime>(event.start);
|
final date = useState<DateTime>(event.start);
|
||||||
final referrer = useState<User?>(null);
|
final referrer = useState<User?>(null);
|
||||||
|
final group = useState<String>("");
|
||||||
|
|
||||||
final canCreate = useMemoized(() {
|
final canCreate = useMemoized(() {
|
||||||
return gamemode.value != null &&
|
return gamemode.value != null &&
|
||||||
@ -883,6 +994,24 @@ class _AddFightWidget extends HookConsumerWidget {
|
|||||||
? "None"
|
? "None"
|
||||||
: "${referrer.value!.name} (${referrer.value!.id})"),
|
: "${referrer.value!.name} (${referrer.value!.id})"),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text("Group"),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final selGroup = await showSearch(
|
||||||
|
context: context,
|
||||||
|
delegate: GroupSearchDelegate(
|
||||||
|
await ref.read(groupsProvider.future)));
|
||||||
|
if (selGroup != null) {
|
||||||
|
if (selGroup.isEmpty) {
|
||||||
|
group.value = "";
|
||||||
|
} else {
|
||||||
|
group.value = selGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(group.value.isEmpty ? "None" : group.value)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@ -903,7 +1032,8 @@ class _AddFightWidget extends HookConsumerWidget {
|
|||||||
map.value!,
|
map.value!,
|
||||||
blueTeam.value!,
|
blueTeam.value!,
|
||||||
redTeam.value!,
|
redTeam.value!,
|
||||||
referrer.value?.id ?? 0);
|
referrer.value?.id ?? 0,
|
||||||
|
group.value.isEmpty ? null : group.value);
|
||||||
onAdded.call();
|
onAdded.call();
|
||||||
nav.pop();
|
nav.pop();
|
||||||
}
|
}
|
||||||
|
@ -317,3 +317,69 @@ class TeamSearchDelegate extends SearchDelegate<Team?> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GroupSearchDelegate extends SearchDelegate<String?> {
|
||||||
|
final List<String> groups;
|
||||||
|
|
||||||
|
GroupSearchDelegate(this.groups);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget>? buildActions(BuildContext context) {
|
||||||
|
return [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
query = "";
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? buildLeading(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
close(context, null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildResults(BuildContext context) {
|
||||||
|
return buildSuggestions(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildSuggestions(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(query),
|
||||||
|
onTap: query.isEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
close(context, query);
|
||||||
|
},
|
||||||
|
subtitle: const Text("Create new group"),
|
||||||
|
leading: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.clear),
|
||||||
|
title: const Text("Reset"),
|
||||||
|
onTap: () {
|
||||||
|
close(context, "");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
for (final group in groups)
|
||||||
|
if (group.toLowerCase().contains(query.toLowerCase()))
|
||||||
|
ListTile(
|
||||||
|
title: Text(group),
|
||||||
|
onTap: () {
|
||||||
|
close(context, group);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -282,6 +282,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
grouped_list:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: grouped_list
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.2"
|
||||||
hooks_riverpod:
|
hooks_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -23,6 +23,7 @@ dependencies:
|
|||||||
go_router: ^6.0.0
|
go_router: ^6.0.0
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
csv: ^5.0.1
|
csv: ^5.0.1
|
||||||
|
grouped_list: ^5.1.2
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren