Updating UI
Dieser Commit ist enthalten in:
Ursprung
9fc40df916
Commit
cdca1c8562
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:steamwar_multitool/src/screens/event.dart';
|
import 'package:steamwar_multitool/src/screens/event.dart';
|
||||||
|
import 'package:steamwar_multitool/src/screens/event_fights_graph.dart';
|
||||||
|
|
||||||
import 'screens/home.dart';
|
import 'screens/home.dart';
|
||||||
import 'screens/login.dart';
|
import 'screens/login.dart';
|
||||||
@ -23,6 +24,16 @@ final _routes = GoRouter(
|
|||||||
}
|
}
|
||||||
return EditEventScreen(eventId);
|
return EditEventScreen(eventId);
|
||||||
}),
|
}),
|
||||||
|
GoRoute(
|
||||||
|
path: "/event/:eventId/graph",
|
||||||
|
builder: (context, state) {
|
||||||
|
final eventId = int.tryParse(state.params['eventId']!);
|
||||||
|
if (eventId == null) {
|
||||||
|
context.go("/");
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
return EventfightsGraph(eventId);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
initialLocation: "/login",
|
initialLocation: "/login",
|
||||||
);
|
);
|
||||||
|
99
lib/src/provider/mods.dart
Normale Datei
99
lib/src/provider/mods.dart
Normale Datei
@ -0,0 +1,99 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:steamwar_multitool/src/provider/http.dart';
|
||||||
|
|
||||||
|
final modRepository = FutureProvider(
|
||||||
|
(ref) async => ModRepository(await ref.read(httpClient.future)),
|
||||||
|
dependencies: [httpClient]);
|
||||||
|
|
||||||
|
final uncheckedMods = FutureProvider<List<Mod>>((ref) async {
|
||||||
|
final repo = await ref.watch(modRepository.future);
|
||||||
|
return repo.listUnchecked();
|
||||||
|
}, dependencies: [modRepository]);
|
||||||
|
|
||||||
|
final mods = FutureProvider<List<Mod>>((ref) async {
|
||||||
|
final repo = await ref.watch(modRepository.future);
|
||||||
|
return repo.listMods();
|
||||||
|
}, dependencies: [modRepository]);
|
||||||
|
|
||||||
|
class ModRepository {
|
||||||
|
final Dio _client;
|
||||||
|
|
||||||
|
ModRepository(this._client);
|
||||||
|
|
||||||
|
Future<List<Mod>> listMods() async {
|
||||||
|
final res = await _client.get("/mods/all");
|
||||||
|
return (res.data as List).map((e) => Mod.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Mod>> listUnchecked() async {
|
||||||
|
final res = await _client.get("/mods/unchecked");
|
||||||
|
return (res.data as List).map((e) => Mod.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateMod(Platform platform, String name, ModType type) {
|
||||||
|
return _client.put("/mods/${platform.name}/$name", data: {
|
||||||
|
"modType": type.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mod {
|
||||||
|
final Platform platform;
|
||||||
|
final String name;
|
||||||
|
final ModType type;
|
||||||
|
|
||||||
|
Mod(this.platform, this.name, this.type);
|
||||||
|
|
||||||
|
factory Mod.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Mod(
|
||||||
|
Platform.fromOrdinal(json["platform"] as int),
|
||||||
|
json["modName"],
|
||||||
|
ModType.fromOrdinal(json["modType"] as int),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Platform {
|
||||||
|
FORGE,
|
||||||
|
LABYMOD,
|
||||||
|
FABRIC;
|
||||||
|
|
||||||
|
static Platform fromOrdinal(int ordinal) {
|
||||||
|
switch (ordinal) {
|
||||||
|
case 0:
|
||||||
|
return FORGE;
|
||||||
|
case 1:
|
||||||
|
return LABYMOD;
|
||||||
|
case 2:
|
||||||
|
return FABRIC;
|
||||||
|
default:
|
||||||
|
throw Exception("Invalid ordinal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ModType {
|
||||||
|
UNKLASSIFIED,
|
||||||
|
GREEN,
|
||||||
|
YELLOW,
|
||||||
|
RED,
|
||||||
|
YOUTUBER_ONLY;
|
||||||
|
|
||||||
|
static ModType fromOrdinal(int ordinal) {
|
||||||
|
switch (ordinal) {
|
||||||
|
case 0:
|
||||||
|
return UNKLASSIFIED;
|
||||||
|
case 1:
|
||||||
|
return GREEN;
|
||||||
|
case 2:
|
||||||
|
return YELLOW;
|
||||||
|
case 3:
|
||||||
|
return RED;
|
||||||
|
case 4:
|
||||||
|
return YOUTUBER_ONLY;
|
||||||
|
default:
|
||||||
|
throw Exception("Invalid ordinal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ class ErrorComponent extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SelectableText(error.toString()),
|
SelectableText(error.toString()),
|
||||||
@ -18,6 +19,7 @@ class ErrorComponent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ class EventListComponent extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
FloatingActionButton.extended(
|
FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(context: context, builder: (context) => EventDialog());
|
showDialog(
|
||||||
|
context: context, builder: (context) => const EventDialog());
|
||||||
},
|
},
|
||||||
label: const Text("Create Event"),
|
label: const Text("Create Event"),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
35
lib/src/screens/components/mod_list.dart
Normale Datei
35
lib/src/screens/components/mod_list.dart
Normale Datei
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:steamwar_multitool/src/provider/mods.dart';
|
||||||
|
|
||||||
|
import 'error.dart';
|
||||||
|
|
||||||
|
class ModListComponent extends HookConsumerWidget {
|
||||||
|
const ModListComponent({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final list = ref.watch(uncheckedMods);
|
||||||
|
|
||||||
|
return list.when(
|
||||||
|
data: (data) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text("WIP",
|
||||||
|
style: Theme.of(context).textTheme.headline5)),
|
||||||
|
...data
|
||||||
|
.map(
|
||||||
|
(e) => ListTile(
|
||||||
|
title: Text(e.name),
|
||||||
|
subtitle: Text(e.platform.name),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error: (err, stack) => ErrorComponent(err, stack),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ 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:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.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';
|
||||||
|
|
||||||
import '../../main.dart';
|
import '../../main.dart';
|
||||||
@ -302,7 +303,8 @@ class _EventScreen extends HookConsumerWidget {
|
|||||||
title: const Text("Use Spectate System"),
|
title: const Text("Use Spectate System"),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text("Teams", style: Theme.of(context).textTheme.headline6),
|
Text("Teams (${eventData.teams.length})",
|
||||||
|
style: Theme.of(context).textTheme.headline6),
|
||||||
Wrap(
|
Wrap(
|
||||||
children: [
|
children: [
|
||||||
for (final team in eventData.teams)
|
for (final team in eventData.teams)
|
||||||
@ -340,6 +342,17 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final event = eventData.event;
|
final event = eventData.event;
|
||||||
final fights = useState<List<EventFight>>(eventData.fights);
|
final fights = useState<List<EventFight>>(eventData.fights);
|
||||||
|
final selected = useState<List<int>>([]);
|
||||||
|
|
||||||
|
final currentCheckBoxState = useMemoized<bool?>(() {
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
return false;
|
||||||
|
} else if (selected.value.length == fights.value.length) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [selected.value, fights.value]);
|
||||||
|
|
||||||
void update() async {
|
void update() async {
|
||||||
final repo = await ref.read(eventRepositoryProvider.future);
|
final repo = await ref.read(eventRepositoryProvider.future);
|
||||||
@ -351,8 +364,192 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
const SizedBox(width: 16),
|
||||||
child: FloatingActionButton.extended(
|
Checkbox(
|
||||||
|
value: currentCheckBoxState,
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) {
|
||||||
|
selected.value = [];
|
||||||
|
} else if (v) {
|
||||||
|
selected.value = fights.value
|
||||||
|
.where((element) => element.start.isAfter(DateTime.now()))
|
||||||
|
.map((e) => e.id)
|
||||||
|
.toList();
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
selected.value = fights.value.map((e) => e.id).toList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selected.value = fights.value.map((e) => e.id).toList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tristate: true,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ButtonBar(
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: "Reschedule Fights",
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return HookBuilder(builder: (context) {
|
||||||
|
final lowest = useMemoized<DateTime>(
|
||||||
|
() => DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
fights.value
|
||||||
|
.where((element) => selected.value
|
||||||
|
.contains(element.id))
|
||||||
|
.map((e) =>
|
||||||
|
e.start.millisecondsSinceEpoch)
|
||||||
|
.reduce(min)),
|
||||||
|
[fights.value]);
|
||||||
|
final date = useState<DateTime>(lowest);
|
||||||
|
|
||||||
|
final offset = useMemoized(
|
||||||
|
() => date.value.difference(lowest),
|
||||||
|
[date.value, lowest]);
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("Reshedule Fights"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
DateTimeEditor(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
(p0) {
|
||||||
|
date.value = p0;
|
||||||
|
},
|
||||||
|
date.value,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
"${offset.isNegative ? "-" : ""}${offset.inHours.abs().toString().padLeft(2, "0")}:${offset.inMinutes.remainder(60).abs().toInt().toString().padLeft(2, "0")}:${offset.inSeconds.remainder(60).abs().toInt().toString().padLeft(2, "0")}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final selectedFights = fights.value
|
||||||
|
.where((element) => selected.value
|
||||||
|
.contains(element.id))
|
||||||
|
.toList();
|
||||||
|
final repo = await ref
|
||||||
|
.read(eventRepositoryProvider.future);
|
||||||
|
for (final fight in selectedFights) {
|
||||||
|
repo.updateFight(
|
||||||
|
fight.id,
|
||||||
|
fight.start.add(offset),
|
||||||
|
fight.gameMode,
|
||||||
|
fight.map,
|
||||||
|
fight.kampfleiter.id,
|
||||||
|
fight.blueTeam,
|
||||||
|
fight.redTeam);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("Reshedule"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.event_note),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: "Change Kampfleiter",
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final kampfleiter = await showSearch(
|
||||||
|
context: context,
|
||||||
|
delegate: UserSearchDelegate(
|
||||||
|
await ref.read(usersProvider.future)));
|
||||||
|
|
||||||
|
if (kampfleiter == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
final repo =
|
||||||
|
await ref.read(eventRepositoryProvider.future);
|
||||||
|
for (final fight in fights.value) {
|
||||||
|
if (selected.value.contains(fight.id)) {
|
||||||
|
repo.updateFight(
|
||||||
|
fight.id,
|
||||||
|
fight.start,
|
||||||
|
fight.gameMode,
|
||||||
|
fight.map,
|
||||||
|
kampfleiter.id,
|
||||||
|
fight.blueTeam,
|
||||||
|
fight.redTeam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.person)),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: "Delete Fights",
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (selected.value.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("Delete Fights"),
|
||||||
|
content: const Text(
|
||||||
|
"Do you really want to delete the selected fights?"),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final repo = await ref
|
||||||
|
.read(eventRepositoryProvider.future);
|
||||||
|
for (final fight in selected.value) {
|
||||||
|
repo.deleteFight(fight);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("Delete",
|
||||||
|
style: TextStyle(color: Colors.red)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete, color: Colors.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -367,14 +564,13 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
label: const Text("Add Fight"),
|
label: const Text("Add Fight"),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return const [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: const Text("Export Data"),
|
|
||||||
value: "export",
|
value: "export",
|
||||||
|
child: Text("Export Data"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@ -386,7 +582,12 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const Divider(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
if (fights.value.isEmpty)
|
||||||
|
const Center(
|
||||||
|
child: Text("No fights yet"),
|
||||||
|
),
|
||||||
for (final fight in fights.value)
|
for (final fight in fights.value)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
@ -427,6 +628,17 @@ class _EventFightList extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
tileColor:
|
tileColor:
|
||||||
fight.score != 0 ? fight.winner.color.withOpacity(0.5) : null,
|
fight.score != 0 ? fight.winner.color.withOpacity(0.5) : null,
|
||||||
|
leading: Checkbox(
|
||||||
|
value: selected.value.contains(fight.id),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value ?? false) {
|
||||||
|
selected.value = [...selected.value, fight.id];
|
||||||
|
} else {
|
||||||
|
selected.value = selected.value
|
||||||
|
.where((element) => element != fight.id)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -474,7 +686,7 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
DateTimeEditor((p0) {
|
DateTimeEditor((p0) {
|
||||||
start.value = p0;
|
start.value = p0;
|
||||||
}, start.value, true),
|
}, start.value, true, mainAxisAlignment: MainAxisAlignment.center),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -540,6 +752,7 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
title: const Text("Delete Fight?"),
|
title: const Text("Delete Fight?"),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
"Do you really want to delete this fight?"),
|
"Do you really want to delete this fight?"),
|
||||||
|
icon: const Icon(Icons.warning),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
@ -561,7 +774,7 @@ class EditEventFightDialog extends HookConsumerWidget {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text("Delete")),
|
child: const Text("Delete", style: TextStyle(color: Colors.red))),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@ -652,7 +865,8 @@ class _AddFightWidget extends HookConsumerWidget {
|
|||||||
_TeamSelector(event, redTeam.value, (p0) => redTeam.value = p0,
|
_TeamSelector(event, redTeam.value, (p0) => redTeam.value = p0,
|
||||||
eventData.teams),
|
eventData.teams),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
DateTimeEditor((p0) => date.value = p0, date.value, true),
|
DateTimeEditor((p0) => date.value = p0, date.value, true,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text("Kampfleiter"),
|
const Text("Kampfleiter"),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -733,13 +947,15 @@ class DateTimeEditor extends HookConsumerWidget {
|
|||||||
final void Function(DateTime) onChanged;
|
final void Function(DateTime) onChanged;
|
||||||
final DateTime initialDate;
|
final DateTime initialDate;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
const DateTimeEditor(this.onChanged, this.initialDate, this.enabled,
|
const DateTimeEditor(this.onChanged, this.initialDate, this.enabled,
|
||||||
{Key? key})
|
{Key? key, this.mainAxisAlignment = MainAxisAlignment.start})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: mainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: enabled
|
onPressed: enabled
|
||||||
|
79
lib/src/screens/event_fights_graph.dart
Normale Datei
79
lib/src/screens/event_fights_graph.dart
Normale Datei
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:steamwar_multitool/src/provider/events.dart';
|
||||||
|
|
||||||
|
class EventfightsGraph extends HookConsumerWidget {
|
||||||
|
final int eventId;
|
||||||
|
const EventfightsGraph(this.eventId, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final eventFuture = useMemoized(() => ref
|
||||||
|
.watch(eventRepositoryProvider.future)
|
||||||
|
.then((value) => value.getEvent(eventId)));
|
||||||
|
return FutureBuilder(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return _EventFightsGraph(snapshot.data!);
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return const Text("Error");
|
||||||
|
} else {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
future: eventFuture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventFightPosition {
|
||||||
|
final Offset position;
|
||||||
|
final int fightId;
|
||||||
|
|
||||||
|
EventFightPosition(this.position, this.fightId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventFightsGraph extends HookConsumerWidget {
|
||||||
|
final EventExtended event;
|
||||||
|
const _EventFightsGraph(this.event, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
//final positions = useState(List.generate(event.fights.length,
|
||||||
|
// (index) => EventFightPosition(Offset.zero, event.fights[index].id)));
|
||||||
|
final positions = useState([
|
||||||
|
EventFightPosition(Offset.zero, 0),
|
||||||
|
EventFightPosition(Offset.zero, 1)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(event.event.name),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
for (final fight in positions.value)
|
||||||
|
Positioned(
|
||||||
|
top: fight.position.dy,
|
||||||
|
left: fight.position.dx,
|
||||||
|
child: GestureDetector(
|
||||||
|
onPanUpdate: (details) {
|
||||||
|
positions.value = [
|
||||||
|
...positions.value
|
||||||
|
.where((element) => element.fightId != fight.fightId),
|
||||||
|
EventFightPosition(
|
||||||
|
fight.position + (details.delta * 2), fight.fightId)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.red,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,13 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:steamwar_multitool/src/provider/mods.dart';
|
||||||
|
|
||||||
import '../provider/events.dart';
|
import '../provider/events.dart';
|
||||||
import '../provider/server.dart';
|
import '../provider/server.dart';
|
||||||
import '../provider/user.dart';
|
import '../provider/user.dart';
|
||||||
import 'components/events_list.dart';
|
import 'components/events_list.dart';
|
||||||
|
import 'components/mod_list.dart';
|
||||||
|
|
||||||
class HomeScreen extends HookConsumerWidget {
|
class HomeScreen extends HookConsumerWidget {
|
||||||
const HomeScreen({Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
@ -46,6 +48,7 @@ class HomeScreen extends HookConsumerWidget {
|
|||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
ref.invalidate(eventsListProvider);
|
ref.invalidate(eventsListProvider);
|
||||||
|
ref.invalidate(uncheckedMods);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
),
|
),
|
||||||
@ -91,8 +94,8 @@ class HomeScreen extends HookConsumerWidget {
|
|||||||
label: Text('Events'),
|
label: Text('Events'),
|
||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
icon: Icon(Icons.dns),
|
icon: Icon(Icons.developer_mode),
|
||||||
label: Text('Server'),
|
label: Text('Mods'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
labelType: NavigationRailLabelType.selected,
|
labelType: NavigationRailLabelType.selected,
|
||||||
@ -114,6 +117,7 @@ class HomeScreen extends HookConsumerWidget {
|
|||||||
//ServerListComponent(),
|
//ServerListComponent(),
|
||||||
EventListComponent(),
|
EventListComponent(),
|
||||||
Placeholder(),
|
Placeholder(),
|
||||||
|
//ModListComponent(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
@ -46,6 +47,10 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => context.go("/"),
|
||||||
|
icon: Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren