1
0
Dieser Commit ist enthalten in:
Chaoscaot 2023-01-22 12:57:13 +01:00
Ursprung cc9d9d2f9a
Commit 12d4752782
49 geänderte Dateien mit 2014 neuen und 2445 gelöschten Zeilen

Datei anzeigen

@ -1,8 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:steamwar_multitool/src/app.dart'; import 'package:steamwar_multitool/src/app.dart';
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm");
void main() => runApp(const ProviderScope(child: DevServerStarterApp())); void main() => runApp(const ProviderScope(child: DevServerStarterApp()));

Datei anzeigen

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:steamwar_multitool/src/screens/event.dart'; import 'package:steamwar_multitool/src/screens/event/event.dart';
import 'package:steamwar_multitool/src/screens/event_fights_graph.dart'; import 'package:steamwar_multitool/src/screens/event/event_fights_graph.dart';
import 'screens/home.dart'; import 'screens/home/home.dart';
import 'screens/login.dart'; import 'screens/login/login.dart';
import 'screens/userinfo.dart'; import 'screens/settings/userinfo.dart';
final _routes = GoRouter( final _routes = GoRouter(
routes: [ routes: [

Datei anzeigen

@ -0,0 +1,2 @@
export 'date_time_editor.dart';
export 'error.dart';

Datei anzeigen

@ -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)),
],
);
}
}

Datei anzeigen

@ -0,0 +1,6 @@
export 'gamemode.dart';
export 'groups.dart';
export 'map.dart';
export 'schematic_type.dart';
export 'team.dart';
export 'user.dart';

Datei anzeigen

@ -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);
},
);
},
);
}
}

Datei anzeigen

@ -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
Datei anzeigen

@ -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);
},
);
},
);
}
}

Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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);
},
)
],
);
}
}

Datei anzeigen

@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/components/components.dart';
import 'package:steamwar_multitool/src/provider/events.dart';
import '../../provider/events.dart'; class CreateEventDialog extends HookConsumerWidget {
import '../event.dart'; const CreateEventDialog({Key? key}) : super(key: key);
class EventDialog extends HookConsumerWidget {
const EventDialog({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -69,9 +68,6 @@ class EventDialog extends HookConsumerWidget {
final event = await ref.read(eventRepositoryProvider.future).then( final event = await ref.read(eventRepositoryProvider.future).then(
(value) => value.createEvent( (value) => value.createEvent(
eventName.text, startTime.value, endTime.value)); eventName.text, startTime.value, endTime.value));
final eventFull = await ref
.read(eventRepositoryProvider.future)
.then((value) => value.getEvent(event.id));
context.go('/event/${event.id}'); context.go('/event/${event.id}');
}, },
child: const Text("Create"), child: const Text("Create"),

Datei anzeigen

@ -0,0 +1 @@
export 'create_event_dialog.dart';

Datei anzeigen

@ -1,7 +1,14 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/types/types.dart';
import 'http.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 fightServersProvider = FutureProvider((ref) async {
final client = await ref.watch(httpClient.future); final client = await ref.watch(httpClient.future);
final res = await client.get("/data/gamemodes"); final res = await client.get("/data/gamemodes");
@ -33,25 +40,3 @@ final usersProvider = FutureProvider((ref) async {
final res = await client.get("/data/users"); final res = await client.get("/data/users");
return (res.data as List<dynamic>).map((e) => User.fromJson(e)).toList(); 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"]);
}
}

Datei anzeigen

@ -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:hooks_riverpod/hooks_riverpod.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:steamwar_multitool/src/repositories/event.dart';
import 'package:steamwar_multitool/src/provider/types.dart'; import 'package:steamwar_multitool/src/types/types.dart';
import 'http.dart'; import 'http.dart';
@ -16,277 +12,3 @@ final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
final repo = await ref.watch(eventRepositoryProvider.future); final repo = await ref.watch(eventRepositoryProvider.future);
return repo.listEvents(); return repo.listEvents();
}, dependencies: [eventRepositoryProvider]); }, dependencies: [eventRepositoryProvider]);
final groupsProvider = FutureProvider.autoDispose<List<String>>((ref) async {
return (await ref.watch(httpClient.future)).get("/data/groups").then((value) {
return (value.data as List).map((e) => e as String).toList();
});
}, dependencies: [httpClient]);
class EventRepository {
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"]));
}
}

Datei anzeigen

@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/provider/http.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( final modRepository = FutureProvider(
(ref) async => ModRepository(await ref.read(httpClient.future)), (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); final repo = await ref.watch(modRepository.future);
return repo.listMods(); return repo.listMods();
}, dependencies: [modRepository]); }, 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");
}
}
}

Datei anzeigen

@ -0,0 +1,5 @@
export 'data.dart';
export 'events.dart';
export 'http.dart';
export 'mods.dart';
export 'user.dart';

Datei anzeigen

@ -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();
});

Datei anzeigen

@ -1,5 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:steamwar_multitool/src/types/types.dart';
final userDataProvider = FutureProvider<UserData>((ref) async { final userDataProvider = FutureProvider<UserData>((ref) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@ -11,13 +12,3 @@ final userDataProvider = FutureProvider<UserData>((ref) async {
uneditablePastEvents: uneditablePastEvents, uneditablePastEvents: uneditablePastEvents,
); );
}); });
class UserData {
final String key;
final bool uneditablePastEvents;
UserData({
required this.key,
required this.uneditablePastEvents,
});
}

Datei anzeigen

@ -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",
});
}
}

Datei anzeigen

@ -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,
});
}
}

Datei anzeigen

@ -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"),
),
],
);
}
}

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -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

Datei anzeigen

@ -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")),
],
);
}
}

Datei anzeigen

@ -0,0 +1,2 @@
export 'add_fight.dart';
export 'edit_fight.dart';

Datei anzeigen

@ -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")),
],
);
}
}

Datei anzeigen

@ -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();
}
}),
),
)
],
);
}
}

Datei anzeigen

@ -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,
),
],
),
),
);
}
}

Datei anzeigen

@ -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"),
);
}
}

Datei anzeigen

@ -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,
);
},
);
}
}

Datei anzeigen

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/provider/events.dart'; import 'package:steamwar_multitool/src/provider/events.dart';
import 'package:steamwar_multitool/src/types/types.dart';
class EventfightsGraph extends HookConsumerWidget { class EventfightsGraph extends HookConsumerWidget {
final int eventId; final int eventId;

Datei anzeigen

@ -6,20 +6,14 @@ import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:steamwar_multitool/src/provider/mods.dart'; import 'package:steamwar_multitool/src/provider/mods.dart';
import 'package:steamwar_multitool/src/provider/provider.dart';
import '../provider/events.dart'; import 'package:steamwar_multitool/src/screens/home/lists/events_list.dart';
import '../provider/server.dart';
import '../provider/user.dart';
import 'components/events_list.dart';
import 'components/mod_list.dart';
class HomeScreen extends HookConsumerWidget { class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key); const HomeScreen({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final servers = ref.watch(serversProvider);
final userData = ref.watch(userDataProvider); final userData = ref.watch(userDataProvider);
if (userData.hasError) { if (userData.hasError) {
@ -44,9 +38,7 @@ class HomeScreen extends HookConsumerWidget {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
IconButton( IconButton(
onPressed: servers.isLoading onPressed: () {
? null
: () {
ref.invalidate(eventsListProvider); ref.invalidate(eventsListProvider);
ref.invalidate(uncheckedMods); ref.invalidate(uncheckedMods);
}, },

Datei anzeigen

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.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 '../../../provider/events.dart';
import 'error.dart';
import 'event_dialog.dart';
class EventListComponent extends HookConsumerWidget { class EventListComponent extends HookConsumerWidget {
const EventListComponent({Key? key}) : super(key: key); const EventListComponent({Key? key}) : super(key: key);
@ -23,7 +23,8 @@ class EventListComponent extends HookConsumerWidget {
FloatingActionButton.extended( FloatingActionButton.extended(
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, builder: (context) => const EventDialog()); context: context,
builder: (context) => const CreateEventDialog());
}, },
label: const Text("Create Event"), label: const Text("Create Event"),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),

Datei anzeigen

@ -1,9 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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 'package:steamwar_multitool/src/provider/mods.dart';
import 'error.dart';
class ModListComponent extends HookConsumerWidget { class ModListComponent extends HookConsumerWidget {
const ModListComponent({Key? key}) : super(key: key); const ModListComponent({Key? key}) : super(key: key);

Datei anzeigen

@ -7,8 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:steamwar_multitool/src/provider/http.dart'; import 'package:steamwar_multitool/src/provider/http.dart';
import 'package:steamwar_multitool/src/provider/provider.dart';
import '../provider/user.dart';
class LoginScreenWidget extends HookConsumerWidget { class LoginScreenWidget extends HookConsumerWidget {
const LoginScreenWidget({ const LoginScreenWidget({

Datei anzeigen

@ -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);
},
)
],
);
}
}

Datei anzeigen

@ -3,9 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:steamwar_multitool/src/components/error.dart';
import '../provider/user.dart'; import 'package:steamwar_multitool/src/provider/provider.dart';
import 'components/error.dart';
class SettingsScreen extends HookConsumerWidget { class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({Key? key}) : super(key: key); const SettingsScreen({Key? key}) : super(key: key);

182
lib/src/types/event.dart Normale Datei
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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
Datei anzeigen

@ -0,0 +1,3 @@
import 'package:intl/intl.dart';
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm");

Datei anzeigen

@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:html'; import 'dart:html';
import 'package:csv/csv.dart'; 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 Start,BlueTeam,RedTeam,WinnerTeam