Refactoring™
Dieser Commit ist enthalten in:
Ursprung
cc9d9d2f9a
Commit
12d4752782
@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:steamwar_multitool/src/app.dart';
|
||||
|
||||
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm");
|
||||
|
||||
void main() => runApp(const ProviderScope(child: DevServerStarterApp()));
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event_fights_graph.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event/event.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event/event_fights_graph.dart';
|
||||
|
||||
import 'screens/home.dart';
|
||||
import 'screens/login.dart';
|
||||
import 'screens/userinfo.dart';
|
||||
import 'screens/home/home.dart';
|
||||
import 'screens/login/login.dart';
|
||||
import 'screens/settings/userinfo.dart';
|
||||
|
||||
final _routes = GoRouter(
|
||||
routes: [
|
||||
|
2
lib/src/components/components.dart
Normale Datei
2
lib/src/components/components.dart
Normale Datei
@ -0,0 +1,2 @@
|
||||
export 'date_time_editor.dart';
|
||||
export 'error.dart';
|
66
lib/src/components/date_time_editor.dart
Normale Datei
66
lib/src/components/date_time_editor.dart
Normale Datei
@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/util/constants.dart';
|
||||
|
||||
class DateTimeEditor extends HookConsumerWidget {
|
||||
final void Function(DateTime) onChanged;
|
||||
final DateTime initialDate;
|
||||
final bool enabled;
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
const DateTimeEditor(this.onChanged, this.initialDate, this.enabled,
|
||||
{Key? key, this.mainAxisAlignment = MainAxisAlignment.start})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Row(
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: enabled
|
||||
? () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2030),
|
||||
);
|
||||
if (date != null) {
|
||||
onChanged(date);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(kDateFormat.format(initialDate)),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: enabled
|
||||
? () async {
|
||||
final time = await showTimePicker(
|
||||
builder: (context, child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
alwaysUse24HourFormat: true,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
context: context,
|
||||
initialTime: TimeOfDay(
|
||||
hour: initialDate.hour, minute: initialDate.minute),
|
||||
);
|
||||
if (time != null) {
|
||||
final changed = DateTime(
|
||||
initialDate.year,
|
||||
initialDate.month,
|
||||
initialDate.day,
|
||||
time.hour,
|
||||
time.minute);
|
||||
onChanged(changed);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.schedule)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
6
lib/src/delegates/delegates.dart
Normale Datei
6
lib/src/delegates/delegates.dart
Normale Datei
@ -0,0 +1,6 @@
|
||||
export 'gamemode.dart';
|
||||
export 'groups.dart';
|
||||
export 'map.dart';
|
||||
export 'schematic_type.dart';
|
||||
export 'team.dart';
|
||||
export 'user.dart';
|
58
lib/src/delegates/gamemode.dart
Normale Datei
58
lib/src/delegates/gamemode.dart
Normale Datei
@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GamemodeSearchDelegate extends SearchDelegate<String?> {
|
||||
final List<String> modes;
|
||||
|
||||
GamemodeSearchDelegate(this.modes);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = modes
|
||||
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length,
|
||||
itemBuilder: (context, index) {
|
||||
final type = out[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
67
lib/src/delegates/groups.dart
Normale Datei
67
lib/src/delegates/groups.dart
Normale Datei
@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
58
lib/src/delegates/map.dart
Normale Datei
58
lib/src/delegates/map.dart
Normale Datei
@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MapSearchDelegate extends SearchDelegate<String?> {
|
||||
final List<String> maps;
|
||||
|
||||
MapSearchDelegate(this.maps);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = maps
|
||||
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length,
|
||||
itemBuilder: (context, index) {
|
||||
final type = out[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
71
lib/src/delegates/schematic_type.dart
Normale Datei
71
lib/src/delegates/schematic_type.dart
Normale Datei
@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
final RESET_TYPE = SchematicType("RESET", "RESET");
|
||||
|
||||
class SchematicTypeSearchDelegate extends SearchDelegate<SchematicType?> {
|
||||
final List<SchematicType> types;
|
||||
|
||||
SchematicTypeSearchDelegate(this.types);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = types
|
||||
.where((element) => element.name.contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return ListTile(
|
||||
title: const Text("Reset"),
|
||||
leading: const Icon(Icons.clear),
|
||||
onTap: () {
|
||||
close(context, RESET_TYPE);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final type = out[index - 1];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type.name),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
74
lib/src/delegates/team.dart
Normale Datei
74
lib/src/delegates/team.dart
Normale Datei
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class TeamSearchDelegate extends SearchDelegate<Team?> {
|
||||
final List<Team> teams;
|
||||
|
||||
TeamSearchDelegate(this.teams);
|
||||
|
||||
@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 ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("?"),
|
||||
onTap: () {
|
||||
close(context, Team(-1, "?", "?", "8"));
|
||||
},
|
||||
),
|
||||
for (final team in teams)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("?"),
|
||||
onTap: () {
|
||||
close(context, Team(-1, "?", "?", "8"));
|
||||
},
|
||||
),
|
||||
for (final team in teams)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
62
lib/src/delegates/user.dart
Normale Datei
62
lib/src/delegates/user.dart
Normale Datei
@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class UserSearchDelegate extends SearchDelegate<User?> {
|
||||
final List<User> users;
|
||||
|
||||
UserSearchDelegate(this.users);
|
||||
|
||||
@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 ListView(
|
||||
children: [
|
||||
for (final team in users)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
for (final team in users)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/components/components.dart';
|
||||
import 'package:steamwar_multitool/src/provider/events.dart';
|
||||
|
||||
import '../../provider/events.dart';
|
||||
import '../event.dart';
|
||||
|
||||
class EventDialog extends HookConsumerWidget {
|
||||
const EventDialog({Key? key}) : super(key: key);
|
||||
class CreateEventDialog extends HookConsumerWidget {
|
||||
const CreateEventDialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -69,9 +68,6 @@ class EventDialog extends HookConsumerWidget {
|
||||
final event = await ref.read(eventRepositoryProvider.future).then(
|
||||
(value) => value.createEvent(
|
||||
eventName.text, startTime.value, endTime.value));
|
||||
final eventFull = await ref
|
||||
.read(eventRepositoryProvider.future)
|
||||
.then((value) => value.getEvent(event.id));
|
||||
context.go('/event/${event.id}');
|
||||
},
|
||||
child: const Text("Create"),
|
1
lib/src/dialogs/dialogs.dart
Normale Datei
1
lib/src/dialogs/dialogs.dart
Normale Datei
@ -0,0 +1 @@
|
||||
export 'create_event_dialog.dart';
|
@ -1,7 +1,14 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
import 'http.dart';
|
||||
|
||||
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]);
|
||||
|
||||
final fightServersProvider = FutureProvider((ref) async {
|
||||
final client = await ref.watch(httpClient.future);
|
||||
final res = await client.get("/data/gamemodes");
|
||||
@ -33,25 +40,3 @@ final usersProvider = FutureProvider((ref) async {
|
||||
final res = await client.get("/data/users");
|
||||
return (res.data as List<dynamic>).map((e) => User.fromJson(e)).toList();
|
||||
});
|
||||
|
||||
class User {
|
||||
final int id;
|
||||
final String name;
|
||||
|
||||
User(this.id, this.name);
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(json["id"] as int, json["name"] as String);
|
||||
}
|
||||
}
|
||||
|
||||
class SchematicType {
|
||||
final String name;
|
||||
final String db;
|
||||
|
||||
SchematicType(this.name, this.db);
|
||||
|
||||
factory SchematicType.fromJson(Map<String, dynamic> json) {
|
||||
return SchematicType(json["name"], json["db"]);
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:steamwar_multitool/src/provider/types.dart';
|
||||
import 'package:steamwar_multitool/src/repositories/event.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
import 'http.dart';
|
||||
|
||||
@ -16,277 +12,3 @@ final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
|
||||
final repo = await ref.watch(eventRepositoryProvider.future);
|
||||
return repo.listEvents();
|
||||
}, 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 {
|
||||
final Dio _client;
|
||||
|
||||
EventRepository(this._client);
|
||||
|
||||
Future<List<ShortEvent>> listEvents() async {
|
||||
final res = await _client.get("/events");
|
||||
return (res.data as List).map((e) => ShortEvent.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<EventExtended> getEvent(int id) async {
|
||||
final res = await _client.get("/events/$id");
|
||||
return EventExtended.fromJson(res.data);
|
||||
}
|
||||
|
||||
Future<void> updateEvent(
|
||||
int id,
|
||||
String name,
|
||||
DateTime deadline,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
int maxTeamMembers,
|
||||
String? schemType,
|
||||
bool publicOnly,
|
||||
bool useSpectateSystem) async {
|
||||
await _client.put("/events/$id", data: {
|
||||
"name": name,
|
||||
"deadline": deadline.millisecondsSinceEpoch,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"end": end.millisecondsSinceEpoch,
|
||||
"maxTeamMembers": maxTeamMembers,
|
||||
"schemType": schemType ?? "null",
|
||||
"publicSchemsOnly": publicOnly,
|
||||
"spectateSystem": useSpectateSystem,
|
||||
});
|
||||
}
|
||||
|
||||
Future<Event> createEvent(String name, DateTime start, DateTime end) async {
|
||||
final res = await _client.post("/events", data: {
|
||||
"name": name,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"end": end.millisecondsSinceEpoch,
|
||||
});
|
||||
return Event.fromJson(res.data);
|
||||
}
|
||||
|
||||
Future<void> deleteEvent(int id) async {
|
||||
await _client.delete("/events/$id");
|
||||
}
|
||||
|
||||
Future<void> deleteFight(int id) async {
|
||||
await _client.delete("/fights/$id");
|
||||
}
|
||||
|
||||
Future<void> createFight(int eventId, DateTime start, String mode, String map,
|
||||
Team blue, Team red, int referee, String? group) {
|
||||
return _client.post("/fights", data: {
|
||||
"event": eventId,
|
||||
"spielmodus": mode,
|
||||
"map": map,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"blueTeam": blue.id,
|
||||
"redTeam": red.id,
|
||||
"kampfleiter": referee,
|
||||
"group": group ?? "null",
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<EventFight>> listFights(int eventId) async {
|
||||
final res = await _client.get("/events/$eventId/fights");
|
||||
return (res.data as List).map((e) => EventFight.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<void> updateFight(int id, DateTime start, String mode, String map,
|
||||
int referee, Team blue, Team red, String? group) {
|
||||
return _client.put("/fights/$id", data: {
|
||||
"spielmodus": mode,
|
||||
"map": map,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"kampfleiter": referee,
|
||||
"blueTeam": blue.id,
|
||||
"redTeam": red.id,
|
||||
"group": group ?? "null",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class EventFight {
|
||||
final int id;
|
||||
final String gameMode;
|
||||
final String map;
|
||||
final DateTime start;
|
||||
final Team redTeam;
|
||||
final Team blueTeam;
|
||||
final User kampfleiter;
|
||||
final int score;
|
||||
final String? group;
|
||||
|
||||
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
|
||||
this.blueTeam, this.kampfleiter, this.score, this.group);
|
||||
|
||||
Team get winner {
|
||||
return getTeamWithContextColor(Colors.white);
|
||||
}
|
||||
|
||||
Team getTeamWithContextColor(Color color) {
|
||||
switch (score) {
|
||||
case 1:
|
||||
return blueTeam;
|
||||
case 2:
|
||||
return redTeam;
|
||||
case 3:
|
||||
return Team(
|
||||
-1, "Tie", "TIE", color.computeLuminance() > 0.5 ? "f" : "0");
|
||||
default:
|
||||
return Team(
|
||||
-1, "Unknown", "UNK", color.computeLuminance() > 0.5 ? "f" : "0");
|
||||
}
|
||||
}
|
||||
|
||||
factory EventFight.fromJson(Map<String, dynamic> json) {
|
||||
return EventFight(
|
||||
json["id"],
|
||||
json["spielmodus"],
|
||||
json["map"],
|
||||
DateTime.fromMillisecondsSinceEpoch(json["start"]),
|
||||
Team.fromJson(json["redTeam"]),
|
||||
Team.fromJson(json["blueTeam"]),
|
||||
User.fromJson(json["kampfleiter"]),
|
||||
json["ergebnis"],
|
||||
json["group"]);
|
||||
}
|
||||
}
|
||||
|
||||
class Team {
|
||||
final int id;
|
||||
final String name;
|
||||
final String kuerzel;
|
||||
final String colorCode;
|
||||
|
||||
Team(this.id, this.name, this.kuerzel, this.colorCode);
|
||||
|
||||
Color get color {
|
||||
switch (colorCode) {
|
||||
case "1":
|
||||
return const Color(0xFF0000AA);
|
||||
case "2":
|
||||
return const Color(0xFF00AA00);
|
||||
case "3":
|
||||
return const Color(0xFF00AAAA);
|
||||
case "4":
|
||||
return const Color(0xFFAA0000);
|
||||
case "5":
|
||||
return const Color(0xFFAA00AA);
|
||||
case "6":
|
||||
return const Color(0xFFFFAA00);
|
||||
case "7":
|
||||
return const Color(0xFFAAAAAA);
|
||||
case "8":
|
||||
return const Color(0xFF555555);
|
||||
case "9":
|
||||
return const Color(0xFF5555FF);
|
||||
case "a":
|
||||
return const Color(0xFF55FF55);
|
||||
case "b":
|
||||
return const Color(0xFF55FFFF);
|
||||
case "c":
|
||||
return const Color(0xFFFF5555);
|
||||
case "d":
|
||||
return const Color(0xFFFF55FF);
|
||||
case "e":
|
||||
return const Color(0xFFFFFF55);
|
||||
case "f":
|
||||
return const Color(0xFFFFFFFF);
|
||||
default:
|
||||
return const Color(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
factory Team.fromJson(Map<String, dynamic> json) {
|
||||
return Team(
|
||||
json['id'] as int,
|
||||
json['name'] as String,
|
||||
json['kuerzel'] as String,
|
||||
json['color'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EventExtended {
|
||||
final Event event;
|
||||
final List<EventFight> fights;
|
||||
final List<Team> teams;
|
||||
|
||||
EventExtended(this.event, this.fights, this.teams);
|
||||
|
||||
factory EventExtended.fromJson(Map<String, dynamic> json) {
|
||||
return EventExtended(
|
||||
Event.fromJson(json['event']),
|
||||
(json['fights'] as List<dynamic>)
|
||||
.map((e) => EventFight.fromJson(e))
|
||||
.toList(),
|
||||
(json['teams'] as List<dynamic>).map((e) => Team.fromJson(e)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Event {
|
||||
final int id;
|
||||
final String name;
|
||||
final DateTime deadline;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
final int maxTeamMembers;
|
||||
final String? schemType;
|
||||
final bool publicSchemsOnly;
|
||||
final bool spectateSystem;
|
||||
|
||||
Event({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.deadline,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.maxTeamMembers,
|
||||
required this.schemType,
|
||||
required this.publicSchemsOnly,
|
||||
required this.spectateSystem,
|
||||
});
|
||||
|
||||
factory Event.fromJson(Map<String, dynamic> json) {
|
||||
return Event(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
deadline: DateTime.fromMillisecondsSinceEpoch(json['deadline']),
|
||||
start: DateTime.fromMillisecondsSinceEpoch(json['start']),
|
||||
end: DateTime.fromMillisecondsSinceEpoch(json['end']),
|
||||
maxTeamMembers: json['maxTeamMembers'],
|
||||
schemType: json['schemType'],
|
||||
publicSchemsOnly: json['publicSchemsOnly'],
|
||||
spectateSystem: json['spectateSystem'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ShortEvent {
|
||||
final String name;
|
||||
final int id;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
ShortEvent(this.name, this.id, this.start, this.end);
|
||||
|
||||
get isUpcoming => start.isAfter(DateTime.now());
|
||||
|
||||
get isCurrent =>
|
||||
start.isBefore(DateTime.now()) && end.isAfter(DateTime.now());
|
||||
|
||||
factory ShortEvent.fromJson(Map<String, dynamic> json) {
|
||||
return ShortEvent(
|
||||
json["name"],
|
||||
json["id"],
|
||||
DateTime.fromMillisecondsSinceEpoch(json["start"]),
|
||||
DateTime.fromMillisecondsSinceEpoch(json["end"]));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/provider/http.dart';
|
||||
import 'package:steamwar_multitool/src/repositories/mod.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
final modRepository = FutureProvider(
|
||||
(ref) async => ModRepository(await ref.read(httpClient.future)),
|
||||
@ -15,85 +16,3 @@ 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
lib/src/provider/provider.dart
Normale Datei
5
lib/src/provider/provider.dart
Normale Datei
@ -0,0 +1,5 @@
|
||||
export 'data.dart';
|
||||
export 'events.dart';
|
||||
export 'http.dart';
|
||||
export 'mods.dart';
|
||||
export 'user.dart';
|
@ -1,23 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../screens/console.dart';
|
||||
|
||||
final serversProvider = FutureProvider<List<String>>((ref) async {
|
||||
return [];
|
||||
//final sftp = await ref.watch(sftpProvider.future);
|
||||
//final ret = (await sftp.readdir("/servers").first)
|
||||
// .map((e) => e.filename)
|
||||
// .where((element) => element != "." && element != "..")
|
||||
// .toList();
|
||||
//ret.sort();
|
||||
//return ret;
|
||||
});
|
||||
|
||||
final favoriteServersProvider = FutureProvider((ref) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final ret = prefs.getStringList("favorite_servers") ?? [];
|
||||
return ret.map((e) => ServerStartParameters.fromJson(jsonDecode(e))).toList();
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
final userDataProvider = FutureProvider<UserData>((ref) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@ -11,13 +12,3 @@ final userDataProvider = FutureProvider<UserData>((ref) async {
|
||||
uneditablePastEvents: uneditablePastEvents,
|
||||
);
|
||||
});
|
||||
|
||||
class UserData {
|
||||
final String key;
|
||||
final bool uneditablePastEvents;
|
||||
|
||||
UserData({
|
||||
required this.key,
|
||||
required this.uneditablePastEvents,
|
||||
});
|
||||
}
|
||||
|
89
lib/src/repositories/event.dart
Normale Datei
89
lib/src/repositories/event.dart
Normale Datei
@ -0,0 +1,89 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class EventRepository {
|
||||
final Dio _client;
|
||||
|
||||
EventRepository(this._client);
|
||||
|
||||
Future<List<ShortEvent>> listEvents() async {
|
||||
final res = await _client.get("/events");
|
||||
return (res.data as List).map((e) => ShortEvent.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<EventExtended> getEvent(int id) async {
|
||||
final res = await _client.get("/events/$id");
|
||||
return EventExtended.fromJson(res.data);
|
||||
}
|
||||
|
||||
Future<void> updateEvent(
|
||||
int id,
|
||||
String name,
|
||||
DateTime deadline,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
int maxTeamMembers,
|
||||
String? schemType,
|
||||
bool publicOnly,
|
||||
bool useSpectateSystem) async {
|
||||
await _client.put("/events/$id", data: {
|
||||
"name": name,
|
||||
"deadline": deadline.millisecondsSinceEpoch,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"end": end.millisecondsSinceEpoch,
|
||||
"maxTeamMembers": maxTeamMembers,
|
||||
"schemType": schemType ?? "null",
|
||||
"publicSchemsOnly": publicOnly,
|
||||
"spectateSystem": useSpectateSystem,
|
||||
});
|
||||
}
|
||||
|
||||
Future<Event> createEvent(String name, DateTime start, DateTime end) async {
|
||||
final res = await _client.post("/events", data: {
|
||||
"name": name,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"end": end.millisecondsSinceEpoch,
|
||||
});
|
||||
return Event.fromJson(res.data);
|
||||
}
|
||||
|
||||
Future<void> deleteEvent(int id) async {
|
||||
await _client.delete("/events/$id");
|
||||
}
|
||||
|
||||
Future<void> deleteFight(int id) async {
|
||||
await _client.delete("/fights/$id");
|
||||
}
|
||||
|
||||
Future<void> createFight(int eventId, DateTime start, String mode, String map,
|
||||
Team blue, Team red, int referee, String? group) {
|
||||
return _client.post("/fights", data: {
|
||||
"event": eventId,
|
||||
"spielmodus": mode,
|
||||
"map": map,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"blueTeam": blue.id,
|
||||
"redTeam": red.id,
|
||||
"kampfleiter": referee,
|
||||
"group": group ?? "null",
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<EventFight>> listFights(int eventId) async {
|
||||
final res = await _client.get("/events/$eventId/fights");
|
||||
return (res.data as List).map((e) => EventFight.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<void> updateFight(int id, DateTime start, String mode, String map,
|
||||
int referee, Team blue, Team red, String? group) {
|
||||
return _client.put("/fights/$id", data: {
|
||||
"spielmodus": mode,
|
||||
"map": map,
|
||||
"start": start.millisecondsSinceEpoch,
|
||||
"kampfleiter": referee,
|
||||
"blueTeam": blue.id,
|
||||
"redTeam": red.id,
|
||||
"group": group ?? "null",
|
||||
});
|
||||
}
|
||||
}
|
24
lib/src/repositories/mod.dart
Normale Datei
24
lib/src/repositories/mod.dart
Normale Datei
@ -0,0 +1,24 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:steamwar_multitool/src/types/mods.dart';
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
@ -1,290 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../provider/server.dart';
|
||||
import '../console.dart';
|
||||
import 'error.dart';
|
||||
|
||||
class ServerListComponent extends HookConsumerWidget {
|
||||
const ServerListComponent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final servers = ref.watch(serversProvider);
|
||||
final favoriteServers = ref.watch(favoriteServersProvider);
|
||||
return servers.when(
|
||||
data: (data) {
|
||||
return ListView(
|
||||
children: [
|
||||
favoriteServers.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text("Favorites"),
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
for (final server in data)
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Card(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ConsoleScreen(
|
||||
server,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(server.name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline6),
|
||||
if (server.world != null)
|
||||
Text("World: ${server.world!}"),
|
||||
if (server.plugins != null)
|
||||
Text("Plugins: ${server.plugins!}"),
|
||||
if (server.port != null)
|
||||
Text("Port: ${server.port!}"),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final remove =
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (contex) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Remove from favorites?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
() {
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop(
|
||||
false);
|
||||
},
|
||||
child: const Text(
|
||||
"Cancel")),
|
||||
TextButton(
|
||||
onPressed:
|
||||
() async {
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop(
|
||||
true);
|
||||
},
|
||||
child: const Text(
|
||||
"Remove")),
|
||||
],
|
||||
);
|
||||
});
|
||||
if (remove) {
|
||||
final favs = data.toList();
|
||||
favs.remove(server);
|
||||
final prefs =
|
||||
await SharedPreferences
|
||||
.getInstance();
|
||||
prefs.setStringList(
|
||||
"favorite_servers",
|
||||
favs
|
||||
.map((e) =>
|
||||
jsonEncode(
|
||||
e.toJson()))
|
||||
.toList());
|
||||
ref.invalidate(
|
||||
favoriteServersProvider);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, stack) => ErrorComponent(err, stack),
|
||||
loading: () => const LinearProgressIndicator()),
|
||||
for (final server in data)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.dns),
|
||||
title: Text(server),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
_CustomizeServerStartParameters(server));
|
||||
},
|
||||
trailing: Tooltip(
|
||||
message: "Quick Start",
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ConsoleScreen(ServerStartParameters(server)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, stack) {
|
||||
return ErrorComponent(err, stack);
|
||||
},
|
||||
loading: () {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomizeServerStartParameters extends HookConsumerWidget {
|
||||
final String server;
|
||||
const _CustomizeServerStartParameters(this.server, {Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final worldNameController = useTextEditingController();
|
||||
final pluginsController = useTextEditingController();
|
||||
final portController = useTextEditingController();
|
||||
final saved = useState(false);
|
||||
|
||||
ServerStartParameters constructParameters() {
|
||||
return ServerStartParameters(
|
||||
server,
|
||||
world:
|
||||
worldNameController.text.isEmpty ? null : worldNameController.text,
|
||||
plugins: pluginsController.text.isEmpty ? null : pluginsController.text,
|
||||
port:
|
||||
portController.text.isEmpty ? null : int.parse(portController.text),
|
||||
);
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: Text("Start $server"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: "World Name",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
controller: worldNameController,
|
||||
onChanged: (d) => saved.value = false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Plugins",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
controller: pluginsController,
|
||||
onChanged: (d) => saved.value = false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
// Allow only Numbers
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Port",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.singleLineFormatter,
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
keyboardType: TextInputType.number,
|
||||
controller: portController,
|
||||
onChanged: (d) => saved.value = false,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final favs = await ref.read(favoriteServersProvider.future);
|
||||
favs.add(constructParameters());
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(
|
||||
"favorite_servers",
|
||||
favs
|
||||
.map((e) => e.toJson())
|
||||
.map((e) => jsonEncode(e))
|
||||
.toList());
|
||||
ref.invalidate(favoriteServersProvider);
|
||||
saved.value = true;
|
||||
},
|
||||
child: Text(saved.value ? "Saved!" : "Save")),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ConsoleScreen(constructParameters()),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text("Start"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
//import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
|
||||
part 'console.g.dart';
|
||||
|
||||
class ConsoleScreen extends StatefulHookConsumerWidget {
|
||||
final ServerStartParameters parameters;
|
||||
const ConsoleScreen(this.parameters, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _ConsoleScreenState();
|
||||
}
|
||||
|
||||
class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
|
||||
//late final SSHSession session;
|
||||
late final Terminal terminal;
|
||||
final _controller = ScrollController(keepScrollOffset: true);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final inputFieldController = useTextEditingController();
|
||||
final inputFieldFocusNode = useFocusNode();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.parameters.name),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
terminal.eraseDisplay();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TerminalView(terminal, alwaysShowCursor: false),
|
||||
),
|
||||
TextField(
|
||||
focusNode: inputFieldFocusNode,
|
||||
onSubmitted: (value) async {
|
||||
inputFieldController.text = "";
|
||||
terminal.write("> $value");
|
||||
terminal.nextLine();
|
||||
//session.write(
|
||||
// Uint8List.fromList(value.codeUnits + '\n'.codeUnits));
|
||||
inputFieldFocusNode.requestFocus();
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Command',
|
||||
),
|
||||
controller: inputFieldController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
super.deactivate();
|
||||
//session.kill(SSHSignal.KILL);
|
||||
//session.close();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
terminal = Terminal();
|
||||
terminal.setLineFeedMode(true);
|
||||
/*final clientFut = ref.read(sshClientProvider);
|
||||
final client = clientFut.value!;
|
||||
client
|
||||
.execute(
|
||||
"python3 /binarys/dev.py ${widget.parameters.name} ${widget.parameters.extraArguments}")
|
||||
.then((value) {
|
||||
session = value;
|
||||
session.stdout.listen((event) {
|
||||
setState(() {
|
||||
terminal.write(String.fromCharCodes(event));
|
||||
});
|
||||
});
|
||||
session.done.then((value) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
if (session.exitCode != 0) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text("Exited with code ${session.exitCode}")));
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServerStartParameters {
|
||||
final String name;
|
||||
final String? plugins;
|
||||
final String? world;
|
||||
final int? port;
|
||||
|
||||
ServerStartParameters(this.name, {this.plugins, this.world, this.port});
|
||||
|
||||
String get extraArguments {
|
||||
final args = <String>[];
|
||||
if (plugins != null) {
|
||||
args.add("-p");
|
||||
args.add(plugins!);
|
||||
}
|
||||
if (world != null) {
|
||||
args.add("-w");
|
||||
args.add(world!);
|
||||
}
|
||||
if (port != null) {
|
||||
args.add("--port");
|
||||
args.add(port.toString());
|
||||
}
|
||||
return args.join(" ");
|
||||
}
|
||||
|
||||
factory ServerStartParameters.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServerStartParametersFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServerStartParametersToJson(this);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'console.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServerStartParameters _$ServerStartParametersFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ServerStartParameters(
|
||||
json['name'] as String,
|
||||
plugins: json['plugins'] as String?,
|
||||
world: json['world'] as String?,
|
||||
port: json['port'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServerStartParametersToJson(
|
||||
ServerStartParameters instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'plugins': instance.plugins,
|
||||
'world': instance.world,
|
||||
'port': instance.port,
|
||||
};
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
147
lib/src/screens/event/components/dialogs/add_fight.dart
Normale Datei
147
lib/src/screens/event/components/dialogs/add_fight.dart
Normale Datei
@ -0,0 +1,147 @@
|
||||
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/date_time_editor.dart';
|
||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event/components/team_selector.dart';
|
||||
import 'package:steamwar_multitool/src/delegates/delegates.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class AddFightDialog extends HookConsumerWidget {
|
||||
final void Function() onAdded;
|
||||
final EventExtended eventData;
|
||||
const AddFightDialog(
|
||||
this.eventData,
|
||||
this.onAdded, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final event = eventData.event;
|
||||
final gamemode = useState<String?>(null);
|
||||
final map = useState<String?>(null);
|
||||
final blueTeam = useState<Team?>(null);
|
||||
final redTeam = useState<Team?>(null);
|
||||
final date = useState<DateTime>(event.start);
|
||||
final referrer = useState<User?>(null);
|
||||
final group = useState<String>("");
|
||||
|
||||
final canCreate = useMemoized(() {
|
||||
return gamemode.value != null &&
|
||||
map.value != null &&
|
||||
blueTeam.value != null &&
|
||||
redTeam.value != null;
|
||||
}, [gamemode.value, map.value, blueTeam.value, redTeam.value]);
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Add Fight"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("Gamemode"),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final modes = await ref.read(fightServersProvider.future);
|
||||
final mode = await showSearch(
|
||||
context: context, delegate: GamemodeSearchDelegate(modes));
|
||||
if (mode != null) {
|
||||
gamemode.value = mode;
|
||||
}
|
||||
},
|
||||
child: Text(gamemode.value ?? "Select"),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Map"),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: gamemode.value != null
|
||||
? () async {
|
||||
final maps = await ref.read(mapsProvider.future);
|
||||
final sMap = await showSearch(
|
||||
context: context,
|
||||
delegate: MapSearchDelegate(maps[gamemode.value]!));
|
||||
if (sMap != null) {
|
||||
map.value = sMap;
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(map.value ?? "Select"),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Blue Team"),
|
||||
TeamSelector(event, blueTeam.value, (p0) => blueTeam.value = p0,
|
||||
eventData.teams),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Red Team"),
|
||||
TeamSelector(event, redTeam.value, (p0) => redTeam.value = p0,
|
||||
eventData.teams),
|
||||
const SizedBox(height: 8),
|
||||
DateTimeEditor((p0) => date.value = p0, date.value, true,
|
||||
mainAxisAlignment: MainAxisAlignment.center),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Kampfleiter"),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final users = await ref.read(usersProvider.future);
|
||||
final user = await showSearch(
|
||||
context: context, delegate: UserSearchDelegate(users));
|
||||
if (user != null) {
|
||||
referrer.value = user;
|
||||
}
|
||||
},
|
||||
child: Text(referrer.value == null || referrer.value!.id == 0
|
||||
? "None"
|
||||
: "${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: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Cancel")),
|
||||
TextButton(
|
||||
onPressed: canCreate
|
||||
? () async {
|
||||
final nav = Navigator.of(context);
|
||||
final repo = await ref.read(eventRepositoryProvider.future);
|
||||
await repo.createFight(
|
||||
event.id,
|
||||
date.value,
|
||||
gamemode.value!,
|
||||
map.value!,
|
||||
blueTeam.value!,
|
||||
redTeam.value!,
|
||||
referrer.value?.id ?? 0,
|
||||
group.value.isEmpty ? null : group.value);
|
||||
onAdded.call();
|
||||
nav.pop();
|
||||
}
|
||||
: null,
|
||||
child: const Text("Add")),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
2
lib/src/screens/event/components/dialogs/dialogs.dart
Normale Datei
2
lib/src/screens/event/components/dialogs/dialogs.dart
Normale Datei
@ -0,0 +1,2 @@
|
||||
export 'add_fight.dart';
|
||||
export 'edit_fight.dart';
|
184
lib/src/screens/event/components/dialogs/edit_fight.dart
Normale Datei
184
lib/src/screens/event/components/dialogs/edit_fight.dart
Normale Datei
@ -0,0 +1,184 @@
|
||||
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/date_time_editor.dart';
|
||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||
import 'package:steamwar_multitool/src/screens/event/components/team_selector.dart';
|
||||
import 'package:steamwar_multitool/src/delegates/delegates.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class EditEventFightDialog extends HookConsumerWidget {
|
||||
const EditEventFightDialog(
|
||||
{Key? key,
|
||||
required this.fight,
|
||||
required this.fightsRefresher,
|
||||
required this.event})
|
||||
: super(key: key);
|
||||
|
||||
final EventFight fight;
|
||||
final EventExtended event;
|
||||
final void Function() fightsRefresher;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final blueTeam = useState(fight.blueTeam);
|
||||
final redTeam = useState(fight.redTeam);
|
||||
final start = useState(fight.start);
|
||||
final mode = useState(fight.gameMode);
|
||||
final map = useState(fight.map);
|
||||
final referrer = useState(fight.kampfleiter);
|
||||
final group = useState(fight.group ?? "");
|
||||
|
||||
return AlertDialog(
|
||||
title: Text("${fight.blueTeam.name} vs ${fight.redTeam.name}"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
const Text("Blue Team"),
|
||||
TeamSelector(event.event, blueTeam.value, (p0) {
|
||||
blueTeam.value = p0;
|
||||
}, event.teams),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Red Team"),
|
||||
TeamSelector(event.event, redTeam.value, (p0) {
|
||||
redTeam.value = p0;
|
||||
}, event.teams),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Start"),
|
||||
const SizedBox(height: 8),
|
||||
DateTimeEditor((p0) {
|
||||
start.value = p0;
|
||||
}, start.value, true, mainAxisAlignment: MainAxisAlignment.center),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
start.value = start.value.add(const Duration(seconds: 30));
|
||||
},
|
||||
child: const Text("Add 30 Seconds"),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Game Mode"),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final modes = await ref.read(fightServersProvider.future);
|
||||
final out = await showSearch(
|
||||
context: context, delegate: GamemodeSearchDelegate(modes));
|
||||
if (out != null) {
|
||||
mode.value = out;
|
||||
}
|
||||
},
|
||||
child: Text(mode.value),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Map"),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final maps = await ref.read(mapsProvider.future);
|
||||
final out = await showSearch(
|
||||
context: context,
|
||||
delegate: MapSearchDelegate(maps[mode.value] ?? []));
|
||||
if (out != null) {
|
||||
map.value = out;
|
||||
fightsRefresher();
|
||||
}
|
||||
},
|
||||
child: Text(map.value),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Kampfleiter"),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final users = await ref.read(usersProvider.future);
|
||||
final user = await showSearch(
|
||||
context: context, delegate: UserSearchDelegate(users));
|
||||
if (user != null) {
|
||||
referrer.value = user;
|
||||
fightsRefresher();
|
||||
}
|
||||
},
|
||||
child: Text(referrer.value.id == 0
|
||||
? "None"
|
||||
: "${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: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final delete = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("Delete Fight?"),
|
||||
content: const Text(
|
||||
"Do you really want to delete this fight?"),
|
||||
icon: const Icon(Icons.warning),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text("No")),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text("Yes",
|
||||
style: TextStyle(color: Colors.red))),
|
||||
],
|
||||
));
|
||||
if (delete) {
|
||||
ref.read(eventRepositoryProvider.future).then(
|
||||
(value) => value.deleteFight(fight.id).then(
|
||||
(value) {
|
||||
fightsRefresher();
|
||||
},
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: const Text("Delete", style: TextStyle(color: Colors.red))),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Cancel")),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var nav = Navigator.of(context);
|
||||
final repo = await ref.read(eventRepositoryProvider.future);
|
||||
await repo.updateFight(
|
||||
fight.id,
|
||||
start.value,
|
||||
mode.value,
|
||||
map.value,
|
||||
referrer.value.id,
|
||||
blueTeam.value,
|
||||
redTeam.value,
|
||||
group.value.isEmpty ? null : group.value);
|
||||
fightsRefresher();
|
||||
nav.pop();
|
||||
},
|
||||
child: const Text("Save")),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
408
lib/src/screens/event/components/fight_list.dart
Normale Datei
408
lib/src/screens/event/components/fight_list.dart
Normale Datei
@ -0,0 +1,408 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:grouped_list/grouped_list.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
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/types/types.dart';
|
||||
import 'package:steamwar_multitool/src/util/constants.dart';
|
||||
import 'package:steamwar_multitool/src/util/event_fight_exporter.dart';
|
||||
|
||||
class EventFightList extends HookConsumerWidget {
|
||||
final EventExtended eventData;
|
||||
|
||||
const EventFightList({Key? key, required this.eventData}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final event = eventData.event;
|
||||
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 {
|
||||
final repo = await ref.read(eventRepositoryProvider.future);
|
||||
fights.value = await repo.listFights(event.id);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
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("Reschedule 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) {
|
||||
await repo.updateFight(
|
||||
fight.id,
|
||||
fight.start.add(offset),
|
||||
fight.gameMode,
|
||||
fight.map,
|
||||
fight.kampfleiter.id,
|
||||
fight.blueTeam,
|
||||
fight.redTeam,
|
||||
fight.group);
|
||||
}
|
||||
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)) {
|
||||
await repo.updateFight(
|
||||
fight.id,
|
||||
fight.start,
|
||||
fight.gameMode,
|
||||
fight.map,
|
||||
kampfleiter.id,
|
||||
fight.blueTeam,
|
||||
fight.redTeam,
|
||||
fight.group);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
},
|
||||
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.isEmpty ? null : group);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.group)),
|
||||
),
|
||||
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) {
|
||||
await 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: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AddFightDialog(
|
||||
eventData,
|
||||
() => update(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
label: const Text("Add Fight"),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return const [
|
||||
PopupMenuItem(
|
||||
value: "export",
|
||||
child: Text("Export Data"),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) {
|
||||
if (value == "export") {
|
||||
openExportedFights(fights.value, event.name);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
if (fights.value.isEmpty)
|
||||
const Center(
|
||||
child: Text("No fights yet"),
|
||||
),
|
||||
GroupedListView(
|
||||
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(
|
||||
"${fight.blueTeam.name} vs ${fight.redTeam.name}",
|
||||
style: TextStyle(
|
||||
color: fight
|
||||
.getTeamWithContextColor(
|
||||
Theme.of(context).scaffoldBackgroundColor)
|
||||
.color
|
||||
.computeLuminance() >
|
||||
0.5
|
||||
? Colors.black
|
||||
: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
fight.score == 0
|
||||
? kDateFormat.format(fight.start)
|
||||
: "Winner: ${fight.winner.name}",
|
||||
style: TextStyle(
|
||||
color: fight
|
||||
.getTeamWithContextColor(
|
||||
Theme.of(context).scaffoldBackgroundColor)
|
||||
.color
|
||||
.computeLuminance() >
|
||||
0.5
|
||||
? Colors.black
|
||||
: Colors.white)),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return EditEventFightDialog(
|
||||
fight: fight,
|
||||
fightsRefresher: () => update(),
|
||||
event: eventData,
|
||||
);
|
||||
});
|
||||
},
|
||||
tileColor:
|
||||
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();
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
283
lib/src/screens/event/components/loaded_event.dart
Normale Datei
283
lib/src/screens/event/components/loaded_event.dart
Normale Datei
@ -0,0 +1,283 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/event.dart';
|
||||
import 'package:steamwar_multitool/src/delegates/delegates.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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
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<SchematicType?>(null);
|
||||
final publicOnlyState = useState(event.publicSchemsOnly);
|
||||
final spectateSystemState = useState(event.spectateSystem);
|
||||
|
||||
useMemoized(() {
|
||||
ref.read(schematicTypesProvider.future).then((value) {
|
||||
schematicTypeState.value = catchToNull(
|
||||
() => value.firstWhere((element) => element.db == event.schemType));
|
||||
});
|
||||
});
|
||||
|
||||
final changed = useState(false);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${"Edit"} ${event.name}"),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("Delete Event"),
|
||||
content: const Text(
|
||||
"Are you sure you want to delete this event?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Cancel")),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(eventRepositoryProvider.future).then(
|
||||
(value) => value.deleteEvent(event.id));
|
||||
ref.invalidate(eventsListProvider);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
"Delete",
|
||||
style: TextStyle(color: Colors.red),
|
||||
)),
|
||||
],
|
||||
));
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: changed.value
|
||||
? () {
|
||||
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,
|
||||
))
|
||||
.whenComplete(
|
||||
() => ref.invalidate(eventsListProvider));
|
||||
context.go('/');
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.save)),
|
||||
],
|
||||
leading: IconButton(
|
||||
onPressed: () async {
|
||||
var canPop = false;
|
||||
if (changed.value) {
|
||||
final accepted = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("Unsaved changes"),
|
||||
content: const Text(
|
||||
"You have unsaved changes. Do you want to discard them?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text("No")),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text("Yes",
|
||||
style: TextStyle(color: Colors.red))),
|
||||
],
|
||||
));
|
||||
if (accepted != null && accepted) {
|
||||
canPop = true;
|
||||
}
|
||||
} else {
|
||||
canPop = true;
|
||||
}
|
||||
if (canPop) context.go("/");
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Name",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) {
|
||||
changed.value = true;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Deadline: "),
|
||||
DateTimeEditor((p0) {
|
||||
deadlineState.value = p0;
|
||||
changed.value = true;
|
||||
}, deadlineState.value, true),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Start: "),
|
||||
DateTimeEditor((p0) {
|
||||
startDateState.value = p0;
|
||||
changed.value = true;
|
||||
}, startDateState.value, true),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Text("End: "),
|
||||
DateTimeEditor((p0) {
|
||||
endDateState.value = p0;
|
||||
changed.value = true;
|
||||
}, endDateState.value, true),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
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;
|
||||
}
|
||||
},
|
||||
),
|
||||
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),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Schematic Type: "),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final types =
|
||||
await ref.read(schematicTypesProvider.future);
|
||||
final out = await showSearch(
|
||||
context: context,
|
||||
delegate: SchematicTypeSearchDelegate(types));
|
||||
if (out == RESET_TYPE) {
|
||||
schematicTypeState.value = null;
|
||||
changed.value = true;
|
||||
} else {
|
||||
schematicTypeState.value =
|
||||
out ?? schematicTypeState.value;
|
||||
changed.value = true;
|
||||
}
|
||||
},
|
||||
child: Text(schematicTypeState.value?.name ?? "Select")),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CheckboxListTile(
|
||||
value: publicOnlyState.value,
|
||||
onChanged: (value) {
|
||||
publicOnlyState.value = value ?? publicOnlyState.value;
|
||||
changed.value = true;
|
||||
},
|
||||
title: const Text("Public Only"),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CheckboxListTile(
|
||||
value: spectateSystemState.value,
|
||||
onChanged: (value) {
|
||||
spectateSystemState.value = value ?? spectateSystemState.value;
|
||||
changed.value = true;
|
||||
},
|
||||
title: const Text("Use Spectate System"),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text("Teams (${eventData.teams.length})",
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
Wrap(
|
||||
children: [
|
||||
for (final team in eventData.teams)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Chip(
|
||||
label: Text(team.name,
|
||||
style: TextStyle(
|
||||
color: team.color.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white)),
|
||||
backgroundColor: team.color,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text("Fights", style: Theme.of(context).textTheme.headline6),
|
||||
EventFightList(
|
||||
eventData: eventData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
33
lib/src/screens/event/components/team_selector.dart
Normale Datei
33
lib/src/screens/event/components/team_selector.dart
Normale Datei
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/delegates/delegates.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class TeamSelector extends HookConsumerWidget {
|
||||
final Event event;
|
||||
final Team? selectedTeam;
|
||||
final void Function(Team) onSelected;
|
||||
final List<Team> teams;
|
||||
|
||||
const TeamSelector(
|
||||
this.event,
|
||||
this.selectedTeam,
|
||||
this.onSelected,
|
||||
this.teams, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
final team = await showSearch(
|
||||
context: context, delegate: TeamSearchDelegate(teams));
|
||||
if (team != null) {
|
||||
onSelected(team);
|
||||
}
|
||||
},
|
||||
child: Text(selectedTeam?.name ?? "Select Team"),
|
||||
);
|
||||
}
|
||||
}
|
57
lib/src/screens/event/event.dart
Normale Datei
57
lib/src/screens/event/event.dart
Normale Datei
@ -0,0 +1,57 @@
|
||||
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';
|
||||
import 'package:steamwar_multitool/src/screens/event/components/loaded_event.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
T? catchToNull<T>(T Function() f) {
|
||||
try {
|
||||
return f();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class EditEventScreen extends HookConsumerWidget {
|
||||
final int eventId;
|
||||
const EditEventScreen(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(
|
||||
future: eventFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Error'),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Error: ${snapshot.error}'),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!snapshot.hasData) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Loading'),
|
||||
),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
final eventData = snapshot.data as EventExtended;
|
||||
|
||||
return LoadedEventScreen(
|
||||
eventData: eventData,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ 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';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class EventfightsGraph extends HookConsumerWidget {
|
||||
final int eventId;
|
@ -6,20 +6,14 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:steamwar_multitool/src/provider/mods.dart';
|
||||
|
||||
import '../provider/events.dart';
|
||||
import '../provider/server.dart';
|
||||
import '../provider/user.dart';
|
||||
import 'components/events_list.dart';
|
||||
import 'components/mod_list.dart';
|
||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||
import 'package:steamwar_multitool/src/screens/home/lists/events_list.dart';
|
||||
|
||||
class HomeScreen extends HookConsumerWidget {
|
||||
const HomeScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final servers = ref.watch(serversProvider);
|
||||
|
||||
final userData = ref.watch(userDataProvider);
|
||||
|
||||
if (userData.hasError) {
|
||||
@ -44,12 +38,10 @@ class HomeScreen extends HookConsumerWidget {
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: servers.isLoading
|
||||
? null
|
||||
: () {
|
||||
ref.invalidate(eventsListProvider);
|
||||
ref.invalidate(uncheckedMods);
|
||||
},
|
||||
onPressed: () {
|
||||
ref.invalidate(eventsListProvider);
|
||||
ref.invalidate(uncheckedMods);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
IconButton(
|
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/main.dart';
|
||||
import 'package:steamwar_multitool/src/components/components.dart';
|
||||
import 'package:steamwar_multitool/src/dialogs/dialogs.dart';
|
||||
import 'package:steamwar_multitool/src/util/constants.dart';
|
||||
|
||||
import '../../provider/events.dart';
|
||||
import 'error.dart';
|
||||
import 'event_dialog.dart';
|
||||
import '../../../provider/events.dart';
|
||||
|
||||
class EventListComponent extends HookConsumerWidget {
|
||||
const EventListComponent({Key? key}) : super(key: key);
|
||||
@ -23,7 +23,8 @@ class EventListComponent extends HookConsumerWidget {
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context, builder: (context) => const EventDialog());
|
||||
context: context,
|
||||
builder: (context) => const CreateEventDialog());
|
||||
},
|
||||
label: const Text("Create Event"),
|
||||
icon: const Icon(Icons.add),
|
@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:steamwar_multitool/src/components/error.dart';
|
||||
import 'package:steamwar_multitool/src/provider/mods.dart';
|
||||
|
||||
import 'error.dart';
|
||||
|
||||
class ModListComponent extends HookConsumerWidget {
|
||||
const ModListComponent({Key? key}) : super(key: key);
|
||||
|
@ -7,8 +7,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:steamwar_multitool/src/provider/http.dart';
|
||||
|
||||
import '../provider/user.dart';
|
||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||
|
||||
class LoginScreenWidget extends HookConsumerWidget {
|
||||
const LoginScreenWidget({
|
@ -1,385 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../provider/events.dart';
|
||||
import '../provider/types.dart';
|
||||
|
||||
final RESET_TYPE = SchematicType("RESET", "RESET");
|
||||
|
||||
class SchematicTypeSearchDelegate extends SearchDelegate<SchematicType?> {
|
||||
final List<SchematicType> types;
|
||||
|
||||
SchematicTypeSearchDelegate(this.types);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = types
|
||||
.where((element) => element.name.contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return ListTile(
|
||||
title: const Text("Reset"),
|
||||
leading: const Icon(Icons.clear),
|
||||
onTap: () {
|
||||
close(context, RESET_TYPE);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final type = out[index - 1];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type.name),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GamemodeSearchDelegate extends SearchDelegate<String?> {
|
||||
final List<String> modes;
|
||||
|
||||
GamemodeSearchDelegate(this.modes);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = modes
|
||||
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length,
|
||||
itemBuilder: (context, index) {
|
||||
final type = out[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MapSearchDelegate extends SearchDelegate<String?> {
|
||||
final List<String> maps;
|
||||
|
||||
MapSearchDelegate(this.maps);
|
||||
|
||||
@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 _buildList(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildList(context);
|
||||
}
|
||||
|
||||
Widget _buildList(BuildContext context) {
|
||||
final out = maps
|
||||
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: out.length,
|
||||
itemBuilder: (context, index) {
|
||||
final type = out[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(type),
|
||||
onTap: () {
|
||||
close(context, type);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserSearchDelegate extends SearchDelegate<User?> {
|
||||
final List<User> users;
|
||||
|
||||
UserSearchDelegate(this.users);
|
||||
|
||||
@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 ListView(
|
||||
children: [
|
||||
for (final team in users)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
for (final team in users)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeamSearchDelegate extends SearchDelegate<Team?> {
|
||||
final List<Team> teams;
|
||||
|
||||
TeamSearchDelegate(this.teams);
|
||||
|
||||
@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 ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("?"),
|
||||
onTap: () {
|
||||
close(context, Team(-1, "?", "?", "8"));
|
||||
},
|
||||
),
|
||||
for (final team in teams)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, team);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("?"),
|
||||
onTap: () {
|
||||
close(context, Team(-1, "?", "?", "8"));
|
||||
},
|
||||
),
|
||||
for (final team in teams)
|
||||
if (team.name.toLowerCase().contains(query.toLowerCase()))
|
||||
ListTile(
|
||||
title: Text(team.name),
|
||||
onTap: () {
|
||||
close(context, 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);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -3,9 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../provider/user.dart';
|
||||
import 'components/error.dart';
|
||||
import 'package:steamwar_multitool/src/components/error.dart';
|
||||
import 'package:steamwar_multitool/src/provider/provider.dart';
|
||||
|
||||
class SettingsScreen extends HookConsumerWidget {
|
||||
const SettingsScreen({Key? key}) : super(key: key);
|
182
lib/src/types/event.dart
Normale Datei
182
lib/src/types/event.dart
Normale Datei
@ -0,0 +1,182 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
class EventFight {
|
||||
final int id;
|
||||
final String gameMode;
|
||||
final String map;
|
||||
final DateTime start;
|
||||
final Team redTeam;
|
||||
final Team blueTeam;
|
||||
final User kampfleiter;
|
||||
final int score;
|
||||
final String? group;
|
||||
|
||||
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
|
||||
this.blueTeam, this.kampfleiter, this.score, this.group);
|
||||
|
||||
Team get winner {
|
||||
return getTeamWithContextColor(Colors.white);
|
||||
}
|
||||
|
||||
Team getTeamWithContextColor(Color color) {
|
||||
switch (score) {
|
||||
case 1:
|
||||
return blueTeam;
|
||||
case 2:
|
||||
return redTeam;
|
||||
case 3:
|
||||
return Team(
|
||||
-1, "Tie", "TIE", color.computeLuminance() > 0.5 ? "f" : "0");
|
||||
default:
|
||||
return Team(
|
||||
-1, "Unknown", "UNK", color.computeLuminance() > 0.5 ? "f" : "0");
|
||||
}
|
||||
}
|
||||
|
||||
factory EventFight.fromJson(Map<String, dynamic> json) {
|
||||
return EventFight(
|
||||
json["id"],
|
||||
json["spielmodus"],
|
||||
json["map"],
|
||||
DateTime.fromMillisecondsSinceEpoch(json["start"]),
|
||||
Team.fromJson(json["redTeam"]),
|
||||
Team.fromJson(json["blueTeam"]),
|
||||
User.fromJson(json["kampfleiter"]),
|
||||
json["ergebnis"],
|
||||
json["group"]);
|
||||
}
|
||||
}
|
||||
|
||||
class Team {
|
||||
final int id;
|
||||
final String name;
|
||||
final String kuerzel;
|
||||
final String colorCode;
|
||||
|
||||
Team(this.id, this.name, this.kuerzel, this.colorCode);
|
||||
|
||||
Color get color {
|
||||
switch (colorCode) {
|
||||
case "1":
|
||||
return const Color(0xFF0000AA);
|
||||
case "2":
|
||||
return const Color(0xFF00AA00);
|
||||
case "3":
|
||||
return const Color(0xFF00AAAA);
|
||||
case "4":
|
||||
return const Color(0xFFAA0000);
|
||||
case "5":
|
||||
return const Color(0xFFAA00AA);
|
||||
case "6":
|
||||
return const Color(0xFFFFAA00);
|
||||
case "7":
|
||||
return const Color(0xFFAAAAAA);
|
||||
case "8":
|
||||
return const Color(0xFF555555);
|
||||
case "9":
|
||||
return const Color(0xFF5555FF);
|
||||
case "a":
|
||||
return const Color(0xFF55FF55);
|
||||
case "b":
|
||||
return const Color(0xFF55FFFF);
|
||||
case "c":
|
||||
return const Color(0xFFFF5555);
|
||||
case "d":
|
||||
return const Color(0xFFFF55FF);
|
||||
case "e":
|
||||
return const Color(0xFFFFFF55);
|
||||
case "f":
|
||||
return const Color(0xFFFFFFFF);
|
||||
default:
|
||||
return const Color(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
factory Team.fromJson(Map<String, dynamic> json) {
|
||||
return Team(
|
||||
json['id'] as int,
|
||||
json['name'] as String,
|
||||
json['kuerzel'] as String,
|
||||
json['color'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EventExtended {
|
||||
final Event event;
|
||||
final List<EventFight> fights;
|
||||
final List<Team> teams;
|
||||
|
||||
EventExtended(this.event, this.fights, this.teams);
|
||||
|
||||
factory EventExtended.fromJson(Map<String, dynamic> json) {
|
||||
return EventExtended(
|
||||
Event.fromJson(json['event']),
|
||||
(json['fights'] as List<dynamic>)
|
||||
.map((e) => EventFight.fromJson(e))
|
||||
.toList(),
|
||||
(json['teams'] as List<dynamic>).map((e) => Team.fromJson(e)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Event {
|
||||
final int id;
|
||||
final String name;
|
||||
final DateTime deadline;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
final int maxTeamMembers;
|
||||
final String? schemType;
|
||||
final bool publicSchemsOnly;
|
||||
final bool spectateSystem;
|
||||
|
||||
Event({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.deadline,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.maxTeamMembers,
|
||||
required this.schemType,
|
||||
required this.publicSchemsOnly,
|
||||
required this.spectateSystem,
|
||||
});
|
||||
|
||||
factory Event.fromJson(Map<String, dynamic> json) {
|
||||
return Event(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
deadline: DateTime.fromMillisecondsSinceEpoch(json['deadline']),
|
||||
start: DateTime.fromMillisecondsSinceEpoch(json['start']),
|
||||
end: DateTime.fromMillisecondsSinceEpoch(json['end']),
|
||||
maxTeamMembers: json['maxTeamMembers'],
|
||||
schemType: json['schemType'],
|
||||
publicSchemsOnly: json['publicSchemsOnly'],
|
||||
spectateSystem: json['spectateSystem'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShortEvent {
|
||||
final String name;
|
||||
final int id;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
ShortEvent(this.name, this.id, this.start, this.end);
|
||||
|
||||
get isUpcoming => start.isAfter(DateTime.now());
|
||||
|
||||
get isCurrent =>
|
||||
start.isBefore(DateTime.now()) && end.isAfter(DateTime.now());
|
||||
|
||||
factory ShortEvent.fromJson(Map<String, dynamic> json) {
|
||||
return ShortEvent(
|
||||
json["name"],
|
||||
json["id"],
|
||||
DateTime.fromMillisecondsSinceEpoch(json["start"]),
|
||||
DateTime.fromMillisecondsSinceEpoch(json["end"]));
|
||||
}
|
||||
}
|
59
lib/src/types/mods.dart
Normale Datei
59
lib/src/types/mods.dart
Normale Datei
@ -0,0 +1,59 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
10
lib/src/types/schematic.dart
Normale Datei
10
lib/src/types/schematic.dart
Normale Datei
@ -0,0 +1,10 @@
|
||||
class SchematicType {
|
||||
final String name;
|
||||
final String db;
|
||||
|
||||
SchematicType(this.name, this.db);
|
||||
|
||||
factory SchematicType.fromJson(Map<String, dynamic> json) {
|
||||
return SchematicType(json["name"], json["db"]);
|
||||
}
|
||||
}
|
5
lib/src/types/types.dart
Normale Datei
5
lib/src/types/types.dart
Normale Datei
@ -0,0 +1,5 @@
|
||||
export 'event.dart';
|
||||
export 'user_data.dart';
|
||||
export 'mods.dart';
|
||||
export 'user.dart';
|
||||
export 'schematic.dart';
|
10
lib/src/types/user.dart
Normale Datei
10
lib/src/types/user.dart
Normale Datei
@ -0,0 +1,10 @@
|
||||
class User {
|
||||
final int id;
|
||||
final String name;
|
||||
|
||||
User(this.id, this.name);
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(json["id"] as int, json["name"] as String);
|
||||
}
|
||||
}
|
9
lib/src/types/user_data.dart
Normale Datei
9
lib/src/types/user_data.dart
Normale Datei
@ -0,0 +1,9 @@
|
||||
class UserData {
|
||||
final String key;
|
||||
final bool uneditablePastEvents;
|
||||
|
||||
UserData({
|
||||
required this.key,
|
||||
required this.uneditablePastEvents,
|
||||
});
|
||||
}
|
3
lib/src/util/constants.dart
Normale Datei
3
lib/src/util/constants.dart
Normale Datei
@ -0,0 +1,3 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm");
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:html';
|
||||
|
||||
import 'package:csv/csv.dart';
|
||||
import 'package:steamwar_multitool/src/provider/events.dart';
|
||||
import 'package:steamwar_multitool/src/types/types.dart';
|
||||
|
||||
/*
|
||||
Start,BlueTeam,RedTeam,WinnerTeam
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren