1
0
Dieser Commit ist enthalten in:
Chaoscaot 2022-12-23 23:12:00 +01:00
Ursprung 907eda9d63
Commit 9cef8feae2
29 geänderte Dateien mit 521 neuen und 837 gelöschten Zeilen

Datei anzeigen

@ -4,7 +4,7 @@
# This file should be version controlled. # This file should be version controlled.
version: version:
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 revision: 135454af32477f815a7525073027a3ff9eff1bfd
channel: stable channel: stable
project_type: app project_type: app
@ -13,17 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: linux - platform: web
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: macos
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
- platform: windows
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
# User provided section # User provided section

Datei anzeigen

@ -1,12 +1,7 @@
import 'dart:io';
import 'package:dev_server_starter/src/app.dart';
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/app.dart';
void main() { void main() {
runApp(const ProviderScope(child: DevServerStarterApp())); runApp(const ProviderScope(child: DevServerStarterApp()));
} }
String? get userHome =>
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];

Datei anzeigen

@ -1,8 +1,8 @@
import 'package:dev_server_starter/src/screens/login.dart';
import 'package:dev_server_starter/src/screens/userinfo.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'screens/home.dart'; import 'screens/home.dart';
import 'screens/login.dart';
import 'screens/userinfo.dart';
class DevServerStarterApp extends StatelessWidget { class DevServerStarterApp extends StatelessWidget {
const DevServerStarterApp({Key? key}) : super(key: key); const DevServerStarterApp({Key? key}) : super(key: key);
@ -15,7 +15,7 @@ class DevServerStarterApp extends StatelessWidget {
"/home": (context) => const HomeScreen(), "/home": (context) => const HomeScreen(),
"/settings": (context) => const SettingsScreen(), "/settings": (context) => const SettingsScreen(),
}, },
title: 'Dev Server Starter', title: 'Event-Tool',
theme: ThemeData( theme: ThemeData(
useMaterial3: true, useMaterial3: true,
colorSchemeSeed: const Color(0xFFFFFF55), colorSchemeSeed: const Color(0xFFFFFF55),

Datei anzeigen

@ -1,56 +1,14 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:dev_server_starter/src/provider/user.dart'; import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mysql_client/mysql_client.dart'; import 'package:json_annotation/json_annotation.dart';
final portProvider = Provider<int>((ref) { import 'http.dart';
return 49507;
});
final portForwardProvider = FutureProvider<int>((ref) async {
final client = await ref.watch(sshClientProvider.future);
final port = ref.watch(portProvider);
final serverSocket = await ServerSocket.bind("127.0.0.1", port);
serverSocket.listen((socket) async {
final forward = await client.forwardLocal('127.0.0.1', 3306);
forward.stream.cast<List<int>>().pipe(socket);
socket.pipe(forward.sink);
});
ref.onDispose(() async {
try {
await serverSocket.close();
} catch (e) {
print(e);
}
});
return serverSocket.port;
});
final mysqlClientProvider = FutureProvider<MySQLConnection>((ref) async {
final port = await ref.watch(portForwardProvider.future);
final userData = await ref.watch(userDataProvider.future);
final conn = await MySQLConnection.createConnection(
host: "localhost",
port: port,
userName: userData.sqlUserName!,
password: userData.sqlPassword!,
secure: false,
databaseName: userData.useDevDB ? "developer" : "core",
);
await conn.connect(timeoutMs: 200).catchError((err) {
ref.invalidate(portForwardProvider);
});
ref.onDispose(() {
if (conn.connected) conn.close();
});
return conn;
}, dependencies: [portForwardProvider, userDataProvider]);
final eventRepositoryProvider = FutureProvider<EventRepository>((ref) async { final eventRepositoryProvider = FutureProvider<EventRepository>((ref) async {
return EventRepository(await ref.watch(mysqlClientProvider.future)); return EventRepository(await ref.read(httpClient.future));
}, dependencies: [mysqlClientProvider]); }, dependencies: [httpClient]);
final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async { final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
final repo = await ref.watch(eventRepositoryProvider.future); final repo = await ref.watch(eventRepositoryProvider.future);
@ -58,43 +16,18 @@ final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
}, dependencies: [eventRepositoryProvider]); }, dependencies: [eventRepositoryProvider]);
class EventRepository { class EventRepository {
final MySQLConnection _connection; final Dio _client;
final _statements = <int, Future<PreparedStmt>>{}; EventRepository(this._client);
EventRepository(this._connection);
Future<PreparedStmt> getStatement(String sql) async {
return _statements.putIfAbsent(
sql.hashCode, () => _connection.prepare(sql));
}
Future<List<ShortEvent>> listEvents() async { Future<List<ShortEvent>> listEvents() async {
final result = await _connection.execute( final res = await _client.get("/events");
"SELECT EventName, EventID, Start, End FROM Event ORDER BY Start DESC"); return (res.data as List).map((e) => ShortEvent.fromJson(e)).toList();
return result.rows
.map((e) => ShortEvent(
e.colByName("EventName")!,
e.typedColByName<int>("EventID")!,
DateTime.parse(e.colByName("Start")!),
DateTime.parse(e.colByName("End")!)))
.toList();
} }
Future<Event> getEvent(int id) async { Future<EventExtended> getEvent(int id) async {
final result = await getStatement("SELECT * FROM Event WHERE EventID = ?") final res = await _client.get("/events/$id");
.then((value) => value.execute([id])); return EventExtended.fromJson(res.data);
return result.rows.map((e) => eventFromResult(e)).first;
}
Future<List<ShortTeam>> getTeams(int eventId) async {
final result = await getStatement(
"SELECT Team.TeamID, TeamName, TeamColor FROM TeamTeilnahme JOIN Team ON TeamTeilnahme.TeamID = Team.TeamID WHERE EventID = ?")
.then((value) => value.execute([eventId]));
return result.rows
.map((e) => ShortTeam(e.typedColByName<int>("TeamID")!,
e.colByName("TeamName")!, e.colByName("TeamColor")!))
.toList();
} }
Future<void> updateEvent( Future<void> updateEvent(
@ -107,76 +40,60 @@ class EventRepository {
String? schemType, String? schemType,
bool publicOnly, bool publicOnly,
bool useSpectateSystem) async { bool useSpectateSystem) async {
await getStatement("" await _client.put("/events/$id", data: {
"UPDATE `Event` SET `EventName`=?,`Deadline`=?,`Start`=?,`End`=?,`MaximumTeamMembers`=?,`SchemType`=?,`PublicSchemsOnly`=?,`SpectateSystem`=? WHERE `EventID` = ?") "name": name,
.then((value) => value.execute([ "deadline": deadline.millisecondsSinceEpoch,
name, "start": start.millisecondsSinceEpoch,
deadline.toIso8601String(), "end": end.millisecondsSinceEpoch,
start.toIso8601String(), "maxTeamMembers": maxTeamMembers,
end.toIso8601String(), "schemType": schemType,
maxTeamMembers, "publicSchemsOnly": publicOnly,
schemType, "spectateSystem": useSpectateSystem,
publicOnly ? 1 : 0, });
useSpectateSystem ? 1 : 0,
id
]));
}
Event eventFromResult(ResultSetRow e) {
return Event(
id: e.typedColByName<int>("EventID")!,
name: e.colByName("EventName")!,
deadline: DateTime.parse(e.colByName("Deadline")!),
start: DateTime.parse(e.colByName("Start")!),
end: DateTime.parse(e.colByName("End")!),
maxTeamMembers: e.typedColByName<int>("MaximumTeamMembers")!,
schemType: e.colByName("SchemType"),
publicOnly: e.typedColByName<bool>("PublicSchemsOnly")!,
useSpectateSystem: e.typedColByName<bool>("SpectateSystem")!,
);
} }
Future<Event> createEvent(String name, DateTime start, DateTime end) async { Future<Event> createEvent(String name, DateTime start, DateTime end) async {
await getStatement( final res = await _client.post("/events", data: {
"INSERT INTO `Event` (`EventName`, `Start`, `End`, `MaximumTeamMembers`, `PublicSchemsOnly`) VALUES (?, ?, ?, 5, 0)") "name": name,
.then((value) => value "start": start.millisecondsSinceEpoch,
.execute([name, start.toIso8601String(), end.toIso8601String()])); "end": end.millisecondsSinceEpoch,
final result = await getStatement("SELECT * FROM Event WHERE EventName = ?") });
.then((value) => value.execute([name])); return Event.fromJson(res.data);
return result.rows.map((e) => eventFromResult(e)).first;
} }
Future<void> deleteEvent(int id) async { Future<void> deleteEvent(int id) async {
await getStatement("DELETE FROM Event WHERE EventID = ?") await _client.delete("/events/$id");
.then((value) => value.execute([id]));
}
Future<List<EventFight>> getFightOfEvent(int id) async {
final result = await getStatement(
"SELECT FightID, StartTime, Spielmodus, Map, tb.TeamID as BlueTeamID, tb.TeamColor as BlueTeamColor, tb.Teamname as BlueTeamName, tr.TeamID as RedTeamID, tr.TeamColor as RedTeamColor, tr.Teamname as RedTeamName, Kampfleiter, Ergebnis FROM EventFight JOIN Team tb ON tb.TeamID = EventFight.TeamBlue JOIN Team tr ON tr.TeamID = EventFight.TeamRed WHERE EventID = ?")
.then((value) => value.execute([id]));
return result.rows.map((e) => EventFight.fromResult(e)).toList();
} }
Future<void> deleteFight(int id) async { Future<void> deleteFight(int id) async {
await getStatement("DELETE FROM EventFight WHERE FightID = ?") await _client.delete("/fights/$id");
.then((value) => value.execute([id]));
} }
Future<void> createFight(int eventId, DateTime start, String mode, String map, Future<void> createFight(int eventId, DateTime start, String mode, String map,
ShortTeam blue, ShortTeam red) { Team blue, Team red) {
return getStatement( return _client.post("/fights", data: {
"INSERT INTO `EventFight` (`EventID`, `StartTime`, `Spielmodus`, `Map`, `TeamBlue`, `TeamRed`, `Kampfleiter`) VALUES (?, ?, ?, ?, ?, ?, 0)") "event": eventId,
.then((value) => value.execute( "spielmodus": mode,
[eventId, start.toIso8601String(), mode, map, blue.id, red.id])); "map": map,
"start": start.millisecondsSinceEpoch,
"blueTeam": blue.id,
"redTeam": red.id,
});
}
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( Future<void> updateFight(
int id, DateTime start, String mode, String map, int referee) { int id, DateTime start, String mode, String map, int referee) {
return getStatement( return _client.put("/fights/$id", data: {
"UPDATE `EventFight` SET `StartTime`=?,`Spielmodus`=?,`Map`=?,`Kampfleiter`=? WHERE `FightID` = ?") "spielmodus": mode,
.then((value) => "map": map,
value.execute([start.toIso8601String(), mode, map, referee, id])); "start": start.millisecondsSinceEpoch,
"kampfleiter": referee,
});
} }
} }
@ -185,49 +102,47 @@ class EventFight {
final String gameMode; final String gameMode;
final String map; final String map;
final DateTime start; final DateTime start;
final ShortTeam redTeam; final Team redTeam;
final ShortTeam blueTeam; final Team blueTeam;
final int fightLeaderId; final int fightLeaderId;
final int score; final int score;
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam, EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
this.blueTeam, this.fightLeaderId, this.score); this.blueTeam, this.fightLeaderId, this.score);
ShortTeam get winner { Team get winner {
switch (score) { switch (score) {
case 1: case 1:
return blueTeam; return blueTeam;
case 2: case 2:
return redTeam; return redTeam;
case 3: case 3:
return ShortTeam(-1, "Tie", "7"); return Team(-1, "Tie", "TIE", "7");
default: default:
return ShortTeam(-1, "Unknown", "7"); return Team(-1, "Unknown", "UNK", "7");
} }
} }
factory EventFight.fromResult(ResultSetRow e) { factory EventFight.fromJson(Map<String, dynamic> json) {
return EventFight( return EventFight(
e.typedColByName<int>("FightID")!, json["id"],
e.colByName("Spielmodus")!, json["spielmodus"],
e.colByName("Map")!, json["map"],
DateTime.parse(e.colByName("StartTime")!), DateTime.fromMillisecondsSinceEpoch(json["start"]),
ShortTeam(e.typedColByName<int>("RedTeamID")!, Team.fromJson(json["redTeam"]),
e.colByName("RedTeamName")!, e.colByName("RedTeamColor")!), Team.fromJson(json["blueTeam"]),
ShortTeam(e.typedColByName<int>("BlueTeamID")!, json["kampfleiter"],
e.colByName("BlueTeamName")!, e.colByName("BlueTeamColor")!), json["ergebnis"]);
e.typedColByName<int>("Kampfleiter")!,
e.typedColByName<int>("Ergebnis")!,
);
} }
} }
class ShortTeam { class Team {
final int id; final int id;
final String name; final String name;
final String kuerzel;
final String colorCode; final String colorCode;
ShortTeam(this.id, this.name, this.colorCode); Team(this.id, this.name, this.kuerzel, this.colorCode);
Color get color { Color get color {
switch (colorCode) { switch (colorCode) {
@ -265,6 +180,33 @@ class ShortTeam {
return const Color(0xFF000000); 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 { class Event {
@ -275,8 +217,8 @@ class Event {
final DateTime end; final DateTime end;
final int maxTeamMembers; final int maxTeamMembers;
final String? schemType; final String? schemType;
final bool publicOnly; final bool publicSchemsOnly;
final bool useSpectateSystem; final bool spectateSystem;
Event({ Event({
required this.id, required this.id,
@ -286,11 +228,26 @@ class Event {
required this.end, required this.end,
required this.maxTeamMembers, required this.maxTeamMembers,
required this.schemType, required this.schemType,
required this.publicOnly, required this.publicSchemsOnly,
required this.useSpectateSystem, 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 { class ShortEvent {
final String name; final String name;
final int id; final int id;
@ -300,4 +257,12 @@ class ShortEvent {
ShortEvent(this.name, this.id, this.start, this.end); ShortEvent(this.name, this.id, this.start, this.end);
get isUpcoming => start.isAfter(DateTime.now()); get isUpcoming => start.isAfter(DateTime.now());
factory ShortEvent.fromJson(Map<String, dynamic> json) {
return ShortEvent(
json["name"],
json["id"],
DateTime.fromMillisecondsSinceEpoch(json["start"]),
DateTime.fromMillisecondsSinceEpoch(json["end"]));
}
} }

28
lib/src/provider/http.dart Normale Datei
Datei anzeigen

@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/provider/user.dart';
final serverUrlProvider = Provider<String>((ref) {
if (kDebugMode) {
return "http://localhost:8000";
} else {
return "https://steamwar.de";
}
});
final httpClient = FutureProvider<Dio>(
(ref) async => Dio(BaseOptions(
baseUrl: ref.watch(serverUrlProvider),
headers: {
"X-SW-Auth":
await ref.watch(userDataProvider.future).then((value) => value.key),
},
contentType: "application/json",
)),
);
final test = FutureProvider((ref) async {
final client = await ref.watch(httpClient.future);
await client.get("/data");
});

Datei anzeigen

@ -1,27 +1,19 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dartssh2/dartssh2.dart';
import 'package:dev_server_starter/src/screens/console.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 'user.dart'; import '../screens/console.dart';
final serversProvider = FutureProvider<List<String>>((ref) async { final serversProvider = FutureProvider<List<String>>((ref) async {
final sftp = await ref.watch(sftpProvider.future); return [];
final ret = (await sftp.readdir("/servers").first) //final sftp = await ref.watch(sftpProvider.future);
.map((e) => e.filename) //final ret = (await sftp.readdir("/servers").first)
.where((element) => element != "." && element != "..") // .map((e) => e.filename)
.toList(); // .where((element) => element != "." && element != "..")
ret.sort(); // .toList();
return ret; //ret.sort();
}); //return ret;
final sftpProvider = FutureProvider<SftpClient>((ref) async {
final client = await ref.watch(sshClientProvider.future);
final sftp = await client.sftp();
ref.onDispose(sftp.close);
return sftp;
}); });
final favoriteServersProvider = FutureProvider((ref) async { final favoriteServersProvider = FutureProvider((ref) async {

Datei anzeigen

@ -1,56 +1,37 @@
import 'package:dev_server_starter/src/provider/events.dart';
import 'package:dev_server_starter/src/provider/server.dart';
import 'package:dev_server_starter/src/provider/user.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:yaml/yaml.dart';
import 'http.dart';
final fightServersProvider = FutureProvider((ref) async { final fightServersProvider = FutureProvider((ref) async {
final client = await ref.watch(sftpProvider.future); final client = await ref.watch(httpClient.future);
return await client.listdir("/configs/GameModes").then( final res = await client.get("/data/gamemodes");
(value) => value return (res.data as List<dynamic>).map((e) => e.toString()).toList();
.map((e) => e.filename)
.where((element) => element != "." && element != "..")
.where((element) =>
element.endsWith(".yml") && !element.endsWith(".kits.yml"))
.map((e) => e.substring(0, e.length - 4))
.toList(),
);
}); });
final schematicTypesProvider = FutureProvider((ref) async { final schematicTypesProvider = FutureProvider((ref) async {
final client = await ref.watch(sshClientProvider.future); return await ref.watch(httpClient.future).then((client) async {
final result = await client final res = await client.get("/data/schematicTypes");
.run("grep ' Type: ' /configs/GameModes/*.yml") return (res.data as List<dynamic>)
.then((value) => String.fromCharCodes(value)); .map((e) => SchematicType.fromJson(e))
return result .toList();
.split("\n") });
.where((element) => element.isNotEmpty)
.map((e) => e.split(": ")[2])
.toList();
}); });
final mapsProvider = FutureProvider<Map<String, List<String>>>((ref) async { final mapsProvider = FutureProvider<Map<String, List<String>>>((ref) async {
final client = await ref.watch(sshClientProvider.future); final client = await ref.watch(httpClient.future);
final gamemodes = await ref.read(fightServersProvider.future); final servers = await ref.watch(fightServersProvider.future);
final maps = <String, List<String>>{}; final ret = <String, List<String>>{};
for (var gm in gamemodes) { for (final server in servers) {
final result = loadYaml(await client final res = await client.get("/data/gamemodes/$server/maps");
.run("cat /configs/GameModes/$gm.yml") ret[server] = (res.data as List<dynamic>).map((e) => e.toString()).toList();
.then((value) => String.fromCharCodes(value)));
final gmMaps = result?["Server"]?["Maps"] as YamlList?;
if (gmMaps != null) {
maps[gm] = gmMaps.toList().map((e) => e.toString()).toList();
}
} }
return maps; return ret;
}); });
final usersProvider = FutureProvider((ref) async { final usersProvider = FutureProvider((ref) async {
final client = await ref.watch(mysqlClientProvider.future); final client = await ref.watch(httpClient.future);
final result = await client.execute("SELECT id, UserName FROM UserData"); final res = await client.get("/data/users");
return result.rows return (res.data as List<dynamic>).map((e) => User.fromJson(e)).toList();
.map((e) => User(e.typedColByName<int>("id")!, e.colByName("UserName")!))
.toList();
}); });
class User { class User {
@ -58,4 +39,19 @@ class User {
final String name; final String name;
User(this.id, this.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,82 +1,23 @@
import 'dart:io';
import 'package:dartssh2/dartssh2.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';
final clientFunctionProvider =
FutureProvider<SSHClient Function(SSHSocket)>((ref) async {
final userData = await ref.watch(userDataProvider.future);
switch (userData.method) {
case 0:
return (socket) => SSHClient(
socket,
username: userData.username,
onPasswordRequest: () => userData.password,
);
case 1:
return (socket) => SSHClient(
socket,
username: userData.username,
identities: [
...SSHKeyPair.fromPem(
File(userData.privateKeyFile!).readAsStringSync()),
],
);
default:
throw Exception("Invalid method");
}
}, dependencies: [userDataProvider]);
final userDataProvider = FutureProvider<UserData>((ref) async { final userDataProvider = FutureProvider<UserData>((ref) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final username = prefs.getString("username")!; final key = prefs.getString("key")!;
final method = prefs.getInt("method")!;
final password = prefs.getString("password");
final privateKey = prefs.getString("privateKeyFile");
final sqlUsername = prefs.getString("sqlUsername");
final sqlPassword = prefs.getString("sqlPassword");
final useDevDB = prefs.getBool("sqlUseDevDB") ?? false;
final uneditablePastEvents = prefs.getBool("uneditablePastEvents") ?? false; final uneditablePastEvents = prefs.getBool("uneditablePastEvents") ?? false;
return UserData( return UserData(
username: username, key: key,
method: method,
password: password,
privateKeyFile: privateKey,
sqlPassword: sqlPassword,
sqlUserName: sqlUsername,
useDevDB: useDevDB,
uneditablePastEvents: uneditablePastEvents, uneditablePastEvents: uneditablePastEvents,
); );
}); });
class UserData { class UserData {
final String username; final String key;
final String? password;
final String? privateKeyFile;
final int method;
final String? sqlUserName;
final String? sqlPassword;
final bool useDevDB;
final bool uneditablePastEvents; final bool uneditablePastEvents;
UserData({ UserData({
required this.username, required this.key,
required this.password,
required this.privateKeyFile,
required this.method,
required this.sqlUserName,
required this.sqlPassword,
required this.useDevDB,
required this.uneditablePastEvents, required this.uneditablePastEvents,
}); });
} }
final sshClientProvider = FutureProvider<SSHClient>((ref) async {
final clientFunction = await ref.watch(clientFunctionProvider.future);
final client = clientFunction(await SSHSocket.connect("steamwar.de", 22));
await client.authenticated;
return client;
}, dependencies: [clientFunctionProvider]);

Datei anzeigen

@ -1,9 +1,10 @@
import 'package:dev_server_starter/src/provider/events.dart';
import 'package:dev_server_starter/src/screens/event.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../provider/events.dart';
import '../event.dart';
class EventDialog extends HookConsumerWidget { class EventDialog extends HookConsumerWidget {
const EventDialog({Key? key}) : super(key: key); const EventDialog({Key? key}) : super(key: key);
@ -68,11 +69,14 @@ 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));
nav.pop(); nav.pop();
nav.push( nav.push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return EditEventScreen(true, event); return EditEventScreen(true, eventFull);
}, },
), ),
); );

Datei anzeigen

@ -1,10 +1,10 @@
import 'package:dev_server_starter/src/provider/events.dart';
import 'package:dev_server_starter/src/provider/user.dart';
import 'package:dev_server_starter/src/screens/components/error.dart';
import 'package:dev_server_starter/src/screens/event.dart';
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 '../../provider/events.dart';
import '../../provider/user.dart';
import '../event.dart';
import 'error.dart';
import 'event_dialog.dart'; import 'event_dialog.dart';
class EventListComponent extends HookConsumerWidget { class EventListComponent extends HookConsumerWidget {
@ -84,20 +84,6 @@ class EventListComponent extends HookConsumerWidget {
], ],
); );
}, error: (err, stack) { }, error: (err, stack) {
final userdata = ref.read(userDataProvider);
if (userdata.value?.sqlUserName == null ||
userdata.value?.sqlPassword == null) {
return const Center(
child: Text("Please set your SQL credentials in the settings"),
);
}
if (err.toString() ==
"MySQLClientException: Can not execute query: connection closed") {
ref.invalidate(mysqlClientProvider);
return const Center(
child: CircularProgressIndicator(),
);
}
return ErrorComponent(err, stack); return ErrorComponent(err, stack);
}, loading: () { }, loading: () {
return const Center( return const Center(

Datei anzeigen

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dev_server_starter/src/screens/components/error.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@ -9,6 +8,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../provider/server.dart'; import '../../provider/server.dart';
import '../console.dart'; import '../console.dart';
import 'error.dart';
class ServerListComponent extends HookConsumerWidget { class ServerListComponent extends HookConsumerWidget {
const ServerListComponent({Key? key}) : super(key: key); const ServerListComponent({Key? key}) : super(key: key);

Datei anzeigen

@ -1,7 +1,6 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:dartssh2/dartssh2.dart'; //import 'package:dartssh2/dartssh2.dart';
import 'package:dev_server_starter/src/provider/user.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -19,7 +18,7 @@ class ConsoleScreen extends StatefulHookConsumerWidget {
} }
class _ConsoleScreenState extends ConsumerState<ConsoleScreen> { class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
late final SSHSession session; //late final SSHSession session;
late final Terminal terminal; late final Terminal terminal;
final _controller = ScrollController(keepScrollOffset: true); final _controller = ScrollController(keepScrollOffset: true);
@ -56,8 +55,8 @@ class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
inputFieldController.text = ""; inputFieldController.text = "";
terminal.write("> $value"); terminal.write("> $value");
terminal.nextLine(); terminal.nextLine();
session.write( //session.write(
Uint8List.fromList(value.codeUnits + '\n'.codeUnits)); // Uint8List.fromList(value.codeUnits + '\n'.codeUnits));
inputFieldFocusNode.requestFocus(); inputFieldFocusNode.requestFocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
@ -81,8 +80,8 @@ class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
@override @override
void deactivate() { void deactivate() {
super.deactivate(); super.deactivate();
session.kill(SSHSignal.KILL); //session.kill(SSHSignal.KILL);
session.close(); //session.close();
} }
@override @override
@ -90,7 +89,7 @@ class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
super.initState(); super.initState();
terminal = Terminal(); terminal = Terminal();
terminal.setLineFeedMode(true); terminal.setLineFeedMode(true);
final clientFut = ref.read(sshClientProvider); /*final clientFut = ref.read(sshClientProvider);
final client = clientFut.value!; final client = clientFut.value!;
client client
.execute( .execute(
@ -111,7 +110,7 @@ class _ConsoleScreenState extends ConsumerState<ConsoleScreen> {
} }
} }
}); });
}); });*/
} }
} }

Datei anzeigen

@ -1,23 +1,30 @@
import 'dart:math'; import 'dart:math';
import 'package:dev_server_starter/src/provider/events.dart';
import 'package:dev_server_starter/src/provider/types.dart';
import 'package:dev_server_starter/src/screens/components/error.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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 '../provider/events.dart';
import '../provider/types.dart';
import 'search_delegates.dart'; import 'search_delegates.dart';
T? catchToNull<T>(T Function() f) {
try {
return f();
} catch (e) {
return null;
}
}
class EditEventScreen extends HookConsumerWidget { class EditEventScreen extends HookConsumerWidget {
final bool editable; final bool editable;
final Event event; final EventExtended eventData;
const EditEventScreen(this.editable, this.event, {Key? key}) const EditEventScreen(this.editable, this.eventData, {Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final event = eventData.event;
final nameController = useTextEditingController(text: event.name); final nameController = useTextEditingController(text: event.name);
final deadlineState = useState(event.deadline); final deadlineState = useState(event.deadline);
final startDateState = useState(event.start); final startDateState = useState(event.start);
@ -26,18 +33,15 @@ class EditEventScreen extends HookConsumerWidget {
final maxTeamMembersController = final maxTeamMembersController =
useTextEditingController(text: event.maxTeamMembers.toString()); useTextEditingController(text: event.maxTeamMembers.toString());
final invalidMaxTeamMembers = useState(false); final invalidMaxTeamMembers = useState(false);
final schematicTypeState = useState(event.schemType); final schematicTypeState = useState(catchToNull(() => ref
final publicOnlyState = useState(event.publicOnly); .watch(schematicTypesProvider)
final spectateSystemState = useState(event.useSpectateSystem); .value!
.firstWhere((element) => element.db == event.schemType)));
final publicOnlyState = useState(event.publicSchemsOnly);
final spectateSystemState = useState(event.spectateSystem);
final changed = useState(false); final changed = useState(false);
final teams = useMemoized(() {
return ref
.read(eventRepositoryProvider.future)
.then((value) => value.getTeams(event.id));
});
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("${editable ? "Edit" : "View"} ${event.name}"), title: Text("${editable ? "Edit" : "View"} ${event.name}"),
@ -85,7 +89,7 @@ class EditEventScreen extends HookConsumerWidget {
startDateState.value, startDateState.value,
endDateState.value, endDateState.value,
maxTeamMembersState.value, maxTeamMembersState.value,
schematicTypeState.value, schematicTypeState.value?.db,
publicOnlyState.value, publicOnlyState.value,
spectateSystemState.value, spectateSystemState.value,
)) ))
@ -223,7 +227,7 @@ class EditEventScreen extends HookConsumerWidget {
final out = await showSearch( final out = await showSearch(
context: context, context: context,
delegate: SchematicTypeSearchDelegate(types)); delegate: SchematicTypeSearchDelegate(types));
if (out == "reset") { if (out == RESET_TYPE) {
schematicTypeState.value = null; schematicTypeState.value = null;
changed.value = true; changed.value = true;
} else { } else {
@ -233,7 +237,7 @@ class EditEventScreen extends HookConsumerWidget {
} }
} }
: null, : null,
child: Text(schematicTypeState.value ?? "Select")), child: Text(schematicTypeState.value?.name ?? "Select")),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -258,36 +262,27 @@ class EditEventScreen extends HookConsumerWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("Teams", style: Theme.of(context).textTheme.headline6), Text("Teams", style: Theme.of(context).textTheme.headline6),
FutureBuilder<List<ShortTeam>>( Wrap(
future: teams, children: [
builder: (context, snap) { for (final team in eventData.teams)
if (snap.hasData) { Padding(
return Wrap( padding: const EdgeInsets.all(8.0),
children: [ child: Chip(
for (final team in snap.data!) label: Text(team.name,
Padding( style: TextStyle(
padding: const EdgeInsets.all(8.0), color: team.color.computeLuminance() > 0.5
child: Chip( ? Colors.black
label: Text(team.name, : Colors.white)),
style: TextStyle( backgroundColor: team.color,
color: team.color.computeLuminance() > 0.5 ),
? Colors.black )
: Colors.white)), ],
backgroundColor: team.color,
),
)
],
);
} else if (snap.hasError) {
return ErrorComponent(snap.error!, null);
} else {
return const LinearProgressIndicator();
}
},
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("Fights", style: Theme.of(context).textTheme.headline6), Text("Fights", style: Theme.of(context).textTheme.headline6),
_EventFightList(event: event), _EventFightList(
eventData: eventData,
),
], ],
), ),
), ),
@ -296,68 +291,58 @@ class EditEventScreen extends HookConsumerWidget {
} }
class _EventFightList extends HookConsumerWidget { class _EventFightList extends HookConsumerWidget {
final Event event; final EventExtended eventData;
const _EventFightList({Key? key, required this.event}) : super(key: key); const _EventFightList({Key? key, required this.eventData}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final fightsRefresher = useState(0); final event = eventData.event;
final fights = useMemoized(() { final fights = useState<List<EventFight>>(eventData.fights);
return ref
.read(eventRepositoryProvider.future)
.then((value) => value.getFightOfEvent(event.id));
}, [fightsRefresher.value]);
return FutureBuilder<List<EventFight>>( void update() async {
future: fights, final repo = await ref.read(eventRepositoryProvider.future);
builder: (context, data) { fights.value = await repo.listFights(event.id);
if (data.hasError) { }
return ErrorComponent(data.error!, null);
} else if (data.hasData) { return ListView(
return ListView( shrinkWrap: true,
shrinkWrap: true, children: [
children: [ const SizedBox(height: 8),
const SizedBox(height: 8), FloatingActionButton.extended(
FloatingActionButton.extended( onPressed: () {
onPressed: () { showDialog(
showDialog( context: context,
context: context, builder: (context) {
builder: (context) { return _AddFightWidget(
return _AddFightWidget( eventData,
event, () => update(),
() => fightsRefresher.value++, );
); },
},
);
},
label: const Text("Add Fight"),
icon: const Icon(Icons.add),
),
const SizedBox(height: 8),
for (final fight in data.data!)
ListTile(
title:
Text("${fight.blueTeam.name} vs ${fight.redTeam.name}"),
subtitle: fight.score != 0
? Text("Winner: ${fight.winner.name}")
: Text(fight.start.toString()),
onTap: () {
showDialog(
context: context,
builder: (context) {
return EditEventFightDialog(
fight: fight, fightsRefresher: fightsRefresher);
});
},
tileColor: fight.score != 0 ? fight.winner.color : null,
)
],
); );
} else { },
return const LinearProgressIndicator(); label: const Text("Add Fight"),
} icon: const Icon(Icons.add),
}); ),
const SizedBox(height: 8),
for (final fight in fights.value)
ListTile(
title: Text("${fight.blueTeam.name} vs ${fight.redTeam.name}"),
subtitle: fight.score != 0
? Text("Winner: ${fight.winner.name}")
: Text(fight.start.toString()),
onTap: () {
showDialog(
context: context,
builder: (context) {
return EditEventFightDialog(
fight: fight, fightsRefresher: () => update());
});
},
tileColor: fight.score != 0 ? fight.winner.color : null,
)
],
);
} }
} }
@ -369,7 +354,7 @@ class EditEventFightDialog extends HookConsumerWidget {
}) : super(key: key); }) : super(key: key);
final EventFight fight; final EventFight fight;
final ValueNotifier<int> fightsRefresher; final void Function() fightsRefresher;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -421,7 +406,7 @@ class EditEventFightDialog extends HookConsumerWidget {
delegate: MapSearchDelegate(maps[mode.value] ?? [])); delegate: MapSearchDelegate(maps[mode.value] ?? []));
if (out != null) { if (out != null) {
map.value = out; map.value = out;
fightsRefresher.value++; fightsRefresher();
} }
}, },
child: Text(map.value), child: Text(map.value),
@ -436,7 +421,7 @@ class EditEventFightDialog extends HookConsumerWidget {
context: context, delegate: UserSearchDelegate(users)); context: context, delegate: UserSearchDelegate(users));
if (user != null) { if (user != null) {
referrer.value = user.id; referrer.value = user.id;
fightsRefresher.value++; fightsRefresher();
} }
}, },
child: child:
@ -451,7 +436,7 @@ class EditEventFightDialog extends HookConsumerWidget {
ref.read(eventRepositoryProvider.future).then( ref.read(eventRepositoryProvider.future).then(
(value) => value.deleteFight(fight.id).then( (value) => value.deleteFight(fight.id).then(
(value) { (value) {
fightsRefresher.value++; fightsRefresher();
}, },
), ),
); );
@ -468,7 +453,7 @@ class EditEventFightDialog extends HookConsumerWidget {
final repo = await ref.read(eventRepositoryProvider.future); final repo = await ref.read(eventRepositoryProvider.future);
await repo.updateFight( await repo.updateFight(
fight.id, start.value, mode.value, map.value, referrer.value); fight.id, start.value, mode.value, map.value, referrer.value);
fightsRefresher.value++; fightsRefresher();
nav.pop(); nav.pop();
}, },
child: const Text("Save")), child: const Text("Save")),
@ -479,19 +464,20 @@ class EditEventFightDialog extends HookConsumerWidget {
class _AddFightWidget extends HookConsumerWidget { class _AddFightWidget extends HookConsumerWidget {
final void Function() onAdded; final void Function() onAdded;
final Event event; final EventExtended eventData;
const _AddFightWidget( const _AddFightWidget(
this.event, this.eventData,
this.onAdded, { this.onAdded, {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final event = eventData.event;
final gamemode = useState<String?>(null); final gamemode = useState<String?>(null);
final map = useState<String?>(null); final map = useState<String?>(null);
final blueTeam = useState<ShortTeam?>(null); final blueTeam = useState<Team?>(null);
final redTeam = useState<ShortTeam?>(null); final redTeam = useState<Team?>(null);
final date = useState<DateTime>(event.start); final date = useState<DateTime>(event.start);
final canCreate = useMemoized(() { final canCreate = useMemoized(() {
@ -538,10 +524,12 @@ class _AddFightWidget extends HookConsumerWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text("Blue Team"), const Text("Blue Team"),
_TeamSelector(event, blueTeam.value, (p0) => blueTeam.value = p0), _TeamSelector(event, blueTeam.value, (p0) => blueTeam.value = p0,
eventData.teams),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text("Red Team"), const Text("Red Team"),
_TeamSelector(event, redTeam.value, (p0) => redTeam.value = p0), _TeamSelector(event, redTeam.value, (p0) => redTeam.value = p0,
eventData.teams),
const SizedBox(height: 8), const SizedBox(height: 8),
DateTimeEditor((p0) => date.value = p0, date.value, true), DateTimeEditor((p0) => date.value = p0, date.value, true),
], ],
@ -576,12 +564,15 @@ class _AddFightWidget extends HookConsumerWidget {
class _TeamSelector extends HookConsumerWidget { class _TeamSelector extends HookConsumerWidget {
final Event event; final Event event;
final ShortTeam? selectedTeam; final Team? selectedTeam;
final void Function(ShortTeam) onSelected; final void Function(Team) onSelected;
final List<Team> teams;
const _TeamSelector( const _TeamSelector(
this.event, this.event,
this.selectedTeam, this.selectedTeam,
this.onSelected, { this.onSelected,
this.teams, {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -589,9 +580,6 @@ class _TeamSelector extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton( return ElevatedButton(
onPressed: () async { onPressed: () async {
final teams = await ref
.read(eventRepositoryProvider.future)
.then((value) => value.getTeams(event.id));
final team = await showSearch( final team = await showSearch(
context: context, delegate: TeamSearchDelegate(teams)); context: context, delegate: TeamSearchDelegate(teams));
if (team != null) { if (team != null) {
@ -635,6 +623,14 @@ class DateTimeEditor extends HookConsumerWidget {
onPressed: enabled onPressed: enabled
? () async { ? () async {
final time = await showTimePicker( final time = await showTimePicker(
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true,
),
child: child!,
);
},
context: context, context: context,
initialTime: TimeOfDay( initialTime: TimeOfDay(
hour: initialDate.hour, minute: initialDate.minute), hour: initialDate.hour, minute: initialDate.minute),

Datei anzeigen

@ -1,14 +1,12 @@
import 'package:dev_server_starter/src/provider/events.dart';
import 'package:dev_server_starter/src/screens/components/events_list.dart';
import 'package:dev_server_starter/src/screens/components/server_list.dart';
import 'package:dev_server_starter/src/screens/console.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package: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 '../provider/events.dart';
import '../provider/server.dart'; import '../provider/server.dart';
import '../provider/user.dart'; import '../provider/user.dart';
import 'components/events_list.dart';
class HomeScreen extends HookConsumerWidget { class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key); const HomeScreen({Key? key}) : super(key: key);
@ -21,12 +19,12 @@ class HomeScreen extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Home'), title: const Text('Home'),
automaticallyImplyLeading: false,
actions: [ actions: [
IconButton( IconButton(
onPressed: servers.isLoading onPressed: servers.isLoading
? null ? null
: () { : () {
ref.refresh(serversProvider);
ref.refresh(eventsListProvider); ref.refresh(eventsListProvider);
}, },
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
@ -48,7 +46,7 @@ class HomeScreen extends HookConsumerWidget {
onPressed: () async { onPressed: () async {
final nav = Navigator.of(context); final nav = Navigator.of(context);
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.remove("username"); await prefs.remove("key");
ref.refresh(userDataProvider); ref.refresh(userDataProvider);
nav.pop(); nav.pop();
nav.pushReplacementNamed("/"); nav.pushReplacementNamed("/");
@ -68,14 +66,14 @@ class HomeScreen extends HookConsumerWidget {
NavigationRail( NavigationRail(
elevation: 1, elevation: 1,
destinations: const [ destinations: const [
NavigationRailDestination(
icon: Icon(Icons.dns),
label: Text('Server'),
),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.calendar_today), icon: Icon(Icons.calendar_today),
label: Text('Events'), label: Text('Events'),
), ),
NavigationRailDestination(
icon: Icon(Icons.dns),
label: Text('Server'),
),
], ],
labelType: NavigationRailLabelType.selected, labelType: NavigationRailLabelType.selected,
trailing: IconButton( trailing: IconButton(
@ -93,8 +91,9 @@ class HomeScreen extends HookConsumerWidget {
child: IndexedStack( child: IndexedStack(
index: navRailIndex.value, index: navRailIndex.value,
children: const [ children: const [
ServerListComponent(), //ServerListComponent(),
EventListComponent(), EventListComponent(),
Placeholder(),
], ],
), ),
), ),

Datei anzeigen

@ -1,14 +1,9 @@
import 'dart:io';
import 'package:dev_server_starter/src/provider/user.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../main.dart'; import '../provider/user.dart';
class LoginScreenWidget extends HookConsumerWidget { class LoginScreenWidget extends HookConsumerWidget {
const LoginScreenWidget({ const LoginScreenWidget({
@ -17,145 +12,60 @@ class LoginScreenWidget extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final clientFunction = ref.watch(clientFunctionProvider); final keyController = useTextEditingController();
final updater = useState(0);
useEffect(() { ref.read(userDataProvider).when(
clientFunction.whenData((clientFunction) { data: (data) {
Navigator.of(context).pushReplacementNamed("/home"); keyController.text = data.key;
}); updater.value++;
return null; },
}, []); loading: () {},
error: (error, stack) {},
);
return clientFunction.when(data: (data) { return Scaffold(
return Scaffold( appBar: AppBar(
appBar: AppBar( title: const Text('Login'),
title: const Text('Login'), automaticallyImplyLeading: false,
), ),
body: Center( body: Center(
child: SizedBox(
width: 200,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text("Redirecting..."), Text(
ElevatedButton( 'Login',
onPressed: () { style: Theme.of(context).textTheme.headline4,
Navigator.of(context).popAndPushNamed("/home"); ),
}, TextField(
child: const Text("Go to home")) decoration: const InputDecoration(
hintText: 'Key',
),
controller: keyController,
obscureText: true,
onChanged: (v) => updater.value++,
),
const SizedBox(
height: 10,
),
FloatingActionButton.large(
onPressed: keyController.text.isNotEmpty
? () async {
final nav = Navigator.of(context);
final prefs = await SharedPreferences.getInstance();
await prefs.setString("key", keyController.text);
ref.invalidate(userDataProvider);
nav.pushReplacementNamed("/home");
}
: null,
child: const Icon(Icons.forward),
)
], ],
), ),
), ),
); ),
}, error: (error, stack) { );
final _userNameField = useTextEditingController();
final _passwordField = useTextEditingController();
final _privateKeyField = useState<String?>(null);
final canProceed = useState(false);
void _checkCanProceed(String _) {
canProceed.value = canProceed.value = _userNameField.text.isNotEmpty &&
(_passwordField.text.isNotEmpty || _privateKeyField.value != null);
}
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Center(
child: SizedBox(
width: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Login',
style: Theme.of(context).textTheme.headline4,
),
TextField(
decoration: const InputDecoration(
hintText: 'Username',
),
controller: _userNameField,
onChanged: _checkCanProceed,
),
const SizedBox(
height: 10,
),
TextField(
decoration: const InputDecoration(
hintText: 'Password',
),
obscureText: true,
controller: _passwordField,
onChanged: _checkCanProceed,
),
const SizedBox(
height: 10,
),
const Text("Or"),
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () async {
final result = await FilePicker.platform.pickFiles(
type: FileType.any,
allowMultiple: false,
dialogTitle: "Select a private key",
initialDirectory:
userHome != null ? join(userHome!, ".ssh") : null,
);
if (result != null) {
final file = result.files.first;
_privateKeyField.value = file.path;
_checkCanProceed("");
}
},
child: const Text('Select Private Key'),
),
if (_privateKeyField.value != null)
Text(_privateKeyField.value!),
const SizedBox(
height: 10,
),
FloatingActionButton.large(
onPressed: canProceed.value
? () async {
final nav = Navigator.of(context);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
"username", _userNameField.text);
if (_privateKeyField.value != null) {
await prefs.setInt("method", 1);
await prefs.setString(
"privateKeyFile", _privateKeyField.value!);
} else {
await prefs.setInt("method", 0);
}
await prefs.setString(
"password", _passwordField.text);
ref.refresh(userDataProvider);
nav.pushReplacementNamed("/home");
}
: null,
child: const Icon(Icons.forward),
)
],
),
),
),
);
}, loading: () {
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: const Center(
child: CircularProgressIndicator(),
),
);
});
} }
} }

Datei anzeigen

@ -1,10 +1,12 @@
import 'package:dev_server_starter/src/provider/types.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../provider/events.dart'; import '../provider/events.dart';
import '../provider/types.dart';
class SchematicTypeSearchDelegate extends SearchDelegate<String?> { final RESET_TYPE = SchematicType("RESET", "RESET");
final List<String> types;
class SchematicTypeSearchDelegate extends SearchDelegate<SchematicType?> {
final List<SchematicType> types;
SchematicTypeSearchDelegate(this.types); SchematicTypeSearchDelegate(this.types);
@ -42,7 +44,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
Widget _buildList(BuildContext context) { Widget _buildList(BuildContext context) {
final out = types final out = types
.where((element) => element.toLowerCase().contains(query.toLowerCase())) .where((element) => element.name.contains(query.toLowerCase()))
.toList(); .toList();
return ListView.builder( return ListView.builder(
itemCount: out.length + 1, itemCount: out.length + 1,
@ -52,7 +54,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
title: const Text("Reset"), title: const Text("Reset"),
leading: const Icon(Icons.clear), leading: const Icon(Icons.clear),
onTap: () { onTap: () {
close(context, "reset"); close(context, RESET_TYPE);
}, },
); );
} }
@ -60,7 +62,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
final type = out[index - 1]; final type = out[index - 1];
return ListTile( return ListTile(
title: Text(type), title: Text(type.name),
onTap: () { onTap: () {
close(context, type); close(context, type);
}, },
@ -244,8 +246,8 @@ class UserSearchDelegate extends SearchDelegate<User?> {
} }
} }
class TeamSearchDelegate extends SearchDelegate<ShortTeam?> { class TeamSearchDelegate extends SearchDelegate<Team?> {
final List<ShortTeam> teams; final List<Team> teams;
TeamSearchDelegate(this.teams); TeamSearchDelegate(this.teams);

Datei anzeigen

@ -1,13 +1,10 @@
import 'package:dev_server_starter/src/provider/user.dart';
import 'package:dev_server_starter/src/screens/components/error.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../main.dart'; import '../provider/user.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);
@ -18,14 +15,7 @@ class SettingsScreen extends HookConsumerWidget {
return userDate.when( return userDate.when(
data: (data) { data: (data) {
final userName = useTextEditingController(text: data.username); final keyController = useTextEditingController(text: data.key);
final password = useTextEditingController(text: data.password ?? "");
final privateKeyFile = useState(data.privateKeyFile);
final sqlUsername =
useTextEditingController(text: data.sqlUserName ?? "");
final sqlPassword =
useTextEditingController(text: data.sqlPassword ?? "");
final useDevDB = useState(data.useDevDB);
final uneditablePastEvents = useState(data.uneditablePastEvents); final uneditablePastEvents = useState(data.uneditablePastEvents);
final changed = useState(false); final changed = useState(false);
@ -37,43 +27,16 @@ class SettingsScreen extends HookConsumerWidget {
IconButton( IconButton(
onPressed: changed.value onPressed: changed.value
? () async { ? () async {
if (userName.text.isEmpty || if (keyController.text.isEmpty) {
(privateKeyFile.value == null &&
password.text.isEmpty)) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text( content: Text("The Key must be Set"),
"Username and password or private key are required"),
), ),
); );
return; return;
} }
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString("username", userName.text); await prefs.setString("key", keyController.text);
if (password.text.isNotEmpty) {
await prefs.setString("password", password.text);
} else {
await prefs.remove("password");
}
if (privateKeyFile.value != null) {
await prefs.setString(
"privateKeyFile", privateKeyFile.value!);
} else {
await prefs.remove("privateKeyFile");
}
if (sqlUsername.text.isNotEmpty) {
await prefs.setString(
"sqlUsername", sqlUsername.text);
} else {
await prefs.remove("sqlUsername");
}
if (sqlPassword.text.isNotEmpty) {
await prefs.setString(
"sqlPassword", sqlPassword.text);
} else {
await prefs.remove("sqlPassword");
}
await prefs.setBool("sqlUseDevDB", useDevDB.value);
await prefs.setBool( await prefs.setBool(
"uneditablePastEvents", uneditablePastEvents.value); "uneditablePastEvents", uneditablePastEvents.value);
ref.refresh(userDataProvider); ref.refresh(userDataProvider);
@ -91,105 +54,17 @@ class SettingsScreen extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
TextField( TextField(
controller: userName, controller: keyController,
decoration: const InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: 'Username', labelText: 'Key',
), ),
onChanged: (value) { onChanged: (value) {
changed.value = true; changed.value = true;
}, },
),
const SizedBox(height: 10),
TextField(
controller: password,
obscureText: true, obscureText: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
onChanged: (value) {
changed.value = true;
},
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
ElevatedButton(
onPressed: () async {
final result = await FilePicker.platform.pickFiles(
type: FileType.any,
allowMultiple: false,
dialogTitle: "Select a private key",
initialDirectory:
userHome != null ? join(userHome!, ".ssh") : null,
);
if (result != null) {
final file = result.files.first;
privateKeyFile.value = file.path;
changed.value = true;
}
},
child:
Text(privateKeyFile.value ?? "Select private key file"),
onLongPress: () async {
final remove = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Remove private key file?"),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text("Remove"),
),
],
);
});
if (remove) {
privateKeyFile.value = null;
changed.value = true;
}
},
),
const SizedBox(height: 10),
TextField(
controller: sqlUsername,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'SQL Username',
),
onChanged: (value) {
changed.value = true;
},
),
const SizedBox(height: 10),
TextField(
controller: sqlPassword,
obscureText: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'SQL Password',
),
onChanged: (value) {
changed.value = true;
},
),
CheckboxListTile(
title: const Text("Use dev database"),
value: useDevDB.value,
onChanged: (value) {
useDevDB.value = value ?? false;
changed.value = true;
},
),
CheckboxListTile( CheckboxListTile(
title: const Text("Disable Past Events uneditable"), title: const Text("Disable Past Events uneditable"),
value: uneditablePastEvents.value, value: uneditablePastEvents.value,

Datei anzeigen

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "SteamWar Dev Server"); gtk_header_bar_set_title(header_bar, "SteamWar Multitool");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "SteamWar Dev Server"); gtk_window_set_title(window, "SteamWar Multitool");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

Datei anzeigen

@ -5,10 +5,8 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import path_provider_macos
import shared_preferences_macos import shared_preferences_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

Datei anzeigen

@ -22,13 +22,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -43,13 +36,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
buffer:
dependency: transitive
description:
name: buffer
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
build: build:
dependency: transitive dependency: transitive
description: description:
@ -169,13 +155,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.4" version: "2.2.4"
dartssh2: dio:
dependency: "direct main" dependency: "direct main"
description: description:
name: dartssh2 name: dio
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.7.3" version: "4.0.6"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@ -204,13 +190,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" version: "6.1.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.4"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -237,13 +216,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_riverpod: flutter_riverpod:
dependency: transitive dependency: transitive
description: description:
@ -387,13 +359,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
mysql_client:
dependency: "direct main"
description:
name: mysql_client
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.27"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -402,33 +367,12 @@ packages:
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
path: path:
dependency: "direct main" dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.8.2"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.22"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -436,13 +380,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.7" version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -457,13 +394,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
pinenacl:
dependency: transitive
description:
name: pinenacl
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -485,13 +415,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -686,13 +609,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -743,7 +659,7 @@ packages:
source: hosted source: hosted
version: "3.4.0" version: "3.4.0"
yaml: yaml:
dependency: "direct main" dependency: transitive
description: description:
name: yaml name: yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

Datei anzeigen

@ -1,4 +1,4 @@
name: dev_server_starter name: steamwar_multitool
description: A new Flutter project. description: A new Flutter project.
authors: authors:
- Chaoscaot - Chaoscaot
@ -14,17 +14,12 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
dartssh2: ^2.7.3
flutter_hooks: ^0.18.5+1 flutter_hooks: ^0.18.5+1
hooks_riverpod: ^2.0.2 hooks_riverpod: ^2.0.2
json_annotation: '>=4.7.0 <4.8.0' json_annotation: '>=4.7.0 <4.8.0'
path: ^1.8.2
path_provider: ^2.0.11
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
file_picker: ^5.2.2
xterm: ^3.4.0 xterm: ^3.4.0
mysql_client: ^0.0.27 dio: ^4.0.6
yaml: ^3.1.1
dev_dependencies: dev_dependencies:

BIN
web/favicon.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 917 B

BIN
web/icons/Icon-192.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 5.2 KiB

BIN
web/icons/Icon-512.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 8.1 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 5.5 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 20 KiB

58
web/index.html Normale Datei
Datei anzeigen

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="dev_server_starter">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Event-Tool</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
});
});
</script>
</body>
</html>

35
web/manifest.json Normale Datei
Datei anzeigen

@ -0,0 +1,35 @@
{
"name": "steamwar_multitool",
"short_name": "steamwar_multitool",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

Datei anzeigen

@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"SteamWar Dev Server", origin, size)) { if (!window.CreateAndShow(L"SteamWar Multitool", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);