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.
version:
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
revision: 135454af32477f815a7525073027a3ff9eff1bfd
channel: stable
project_type: app
@ -13,17 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
- platform: linux
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
- platform: macos
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
- platform: windows
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: web
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
# 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:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/app.dart';
void main() {
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 'screens/home.dart';
import 'screens/login.dart';
import 'screens/userinfo.dart';
class DevServerStarterApp extends StatelessWidget {
const DevServerStarterApp({Key? key}) : super(key: key);
@ -15,7 +15,7 @@ class DevServerStarterApp extends StatelessWidget {
"/home": (context) => const HomeScreen(),
"/settings": (context) => const SettingsScreen(),
},
title: 'Dev Server Starter',
title: 'Event-Tool',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: const Color(0xFFFFFF55),

Datei anzeigen

@ -1,56 +1,14 @@
import 'dart:io';
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:mysql_client/mysql_client.dart';
import 'package:json_annotation/json_annotation.dart';
final portProvider = Provider<int>((ref) {
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]);
import 'http.dart';
final eventRepositoryProvider = FutureProvider<EventRepository>((ref) async {
return EventRepository(await ref.watch(mysqlClientProvider.future));
}, dependencies: [mysqlClientProvider]);
return EventRepository(await ref.read(httpClient.future));
}, dependencies: [httpClient]);
final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
final repo = await ref.watch(eventRepositoryProvider.future);
@ -58,43 +16,18 @@ final eventsListProvider = FutureProvider<List<ShortEvent>>((ref) async {
}, dependencies: [eventRepositoryProvider]);
class EventRepository {
final MySQLConnection _connection;
final Dio _client;
final _statements = <int, Future<PreparedStmt>>{};
EventRepository(this._connection);
Future<PreparedStmt> getStatement(String sql) async {
return _statements.putIfAbsent(
sql.hashCode, () => _connection.prepare(sql));
}
EventRepository(this._client);
Future<List<ShortEvent>> listEvents() async {
final result = await _connection.execute(
"SELECT EventName, EventID, Start, End FROM Event ORDER BY Start DESC");
return result.rows
.map((e) => ShortEvent(
e.colByName("EventName")!,
e.typedColByName<int>("EventID")!,
DateTime.parse(e.colByName("Start")!),
DateTime.parse(e.colByName("End")!)))
.toList();
final res = await _client.get("/events");
return (res.data as List).map((e) => ShortEvent.fromJson(e)).toList();
}
Future<Event> getEvent(int id) async {
final result = await getStatement("SELECT * FROM Event WHERE EventID = ?")
.then((value) => value.execute([id]));
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<EventExtended> getEvent(int id) async {
final res = await _client.get("/events/$id");
return EventExtended.fromJson(res.data);
}
Future<void> updateEvent(
@ -107,76 +40,60 @@ class EventRepository {
String? schemType,
bool publicOnly,
bool useSpectateSystem) async {
await getStatement(""
"UPDATE `Event` SET `EventName`=?,`Deadline`=?,`Start`=?,`End`=?,`MaximumTeamMembers`=?,`SchemType`=?,`PublicSchemsOnly`=?,`SpectateSystem`=? WHERE `EventID` = ?")
.then((value) => value.execute([
name,
deadline.toIso8601String(),
start.toIso8601String(),
end.toIso8601String(),
maxTeamMembers,
schemType,
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")!,
);
await _client.put("/events/$id", data: {
"name": name,
"deadline": deadline.millisecondsSinceEpoch,
"start": start.millisecondsSinceEpoch,
"end": end.millisecondsSinceEpoch,
"maxTeamMembers": maxTeamMembers,
"schemType": schemType,
"publicSchemsOnly": publicOnly,
"spectateSystem": useSpectateSystem,
});
}
Future<Event> createEvent(String name, DateTime start, DateTime end) async {
await getStatement(
"INSERT INTO `Event` (`EventName`, `Start`, `End`, `MaximumTeamMembers`, `PublicSchemsOnly`) VALUES (?, ?, ?, 5, 0)")
.then((value) => value
.execute([name, start.toIso8601String(), end.toIso8601String()]));
final result = await getStatement("SELECT * FROM Event WHERE EventName = ?")
.then((value) => value.execute([name]));
return result.rows.map((e) => eventFromResult(e)).first;
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 getStatement("DELETE FROM Event WHERE EventID = ?")
.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();
await _client.delete("/events/$id");
}
Future<void> deleteFight(int id) async {
await getStatement("DELETE FROM EventFight WHERE FightID = ?")
.then((value) => value.execute([id]));
await _client.delete("/fights/$id");
}
Future<void> createFight(int eventId, DateTime start, String mode, String map,
ShortTeam blue, ShortTeam red) {
return getStatement(
"INSERT INTO `EventFight` (`EventID`, `StartTime`, `Spielmodus`, `Map`, `TeamBlue`, `TeamRed`, `Kampfleiter`) VALUES (?, ?, ?, ?, ?, ?, 0)")
.then((value) => value.execute(
[eventId, start.toIso8601String(), mode, map, blue.id, red.id]));
Team blue, Team red) {
return _client.post("/fights", data: {
"event": eventId,
"spielmodus": mode,
"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(
int id, DateTime start, String mode, String map, int referee) {
return getStatement(
"UPDATE `EventFight` SET `StartTime`=?,`Spielmodus`=?,`Map`=?,`Kampfleiter`=? WHERE `FightID` = ?")
.then((value) =>
value.execute([start.toIso8601String(), mode, map, referee, id]));
return _client.put("/fights/$id", data: {
"spielmodus": mode,
"map": map,
"start": start.millisecondsSinceEpoch,
"kampfleiter": referee,
});
}
}
@ -185,49 +102,47 @@ class EventFight {
final String gameMode;
final String map;
final DateTime start;
final ShortTeam redTeam;
final ShortTeam blueTeam;
final Team redTeam;
final Team blueTeam;
final int fightLeaderId;
final int score;
EventFight(this.id, this.gameMode, this.map, this.start, this.redTeam,
this.blueTeam, this.fightLeaderId, this.score);
ShortTeam get winner {
Team get winner {
switch (score) {
case 1:
return blueTeam;
case 2:
return redTeam;
case 3:
return ShortTeam(-1, "Tie", "7");
return Team(-1, "Tie", "TIE", "7");
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(
e.typedColByName<int>("FightID")!,
e.colByName("Spielmodus")!,
e.colByName("Map")!,
DateTime.parse(e.colByName("StartTime")!),
ShortTeam(e.typedColByName<int>("RedTeamID")!,
e.colByName("RedTeamName")!, e.colByName("RedTeamColor")!),
ShortTeam(e.typedColByName<int>("BlueTeamID")!,
e.colByName("BlueTeamName")!, e.colByName("BlueTeamColor")!),
e.typedColByName<int>("Kampfleiter")!,
e.typedColByName<int>("Ergebnis")!,
);
json["id"],
json["spielmodus"],
json["map"],
DateTime.fromMillisecondsSinceEpoch(json["start"]),
Team.fromJson(json["redTeam"]),
Team.fromJson(json["blueTeam"]),
json["kampfleiter"],
json["ergebnis"]);
}
}
class ShortTeam {
class Team {
final int id;
final String name;
final String kuerzel;
final String colorCode;
ShortTeam(this.id, this.name, this.colorCode);
Team(this.id, this.name, this.kuerzel, this.colorCode);
Color get color {
switch (colorCode) {
@ -265,6 +180,33 @@ class ShortTeam {
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 {
@ -275,8 +217,8 @@ class Event {
final DateTime end;
final int maxTeamMembers;
final String? schemType;
final bool publicOnly;
final bool useSpectateSystem;
final bool publicSchemsOnly;
final bool spectateSystem;
Event({
required this.id,
@ -286,11 +228,26 @@ class Event {
required this.end,
required this.maxTeamMembers,
required this.schemType,
required this.publicOnly,
required this.useSpectateSystem,
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;
@ -300,4 +257,12 @@ class ShortEvent {
ShortEvent(this.name, this.id, this.start, this.end);
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 'package:dartssh2/dartssh2.dart';
import 'package:dev_server_starter/src/screens/console.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'user.dart';
import '../screens/console.dart';
final serversProvider = FutureProvider<List<String>>((ref) async {
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 sftpProvider = FutureProvider<SftpClient>((ref) async {
final client = await ref.watch(sshClientProvider.future);
final sftp = await client.sftp();
ref.onDispose(sftp.close);
return sftp;
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 {

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:yaml/yaml.dart';
import 'http.dart';
final fightServersProvider = FutureProvider((ref) async {
final client = await ref.watch(sftpProvider.future);
return await client.listdir("/configs/GameModes").then(
(value) => value
.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 client = await ref.watch(httpClient.future);
final res = await client.get("/data/gamemodes");
return (res.data as List<dynamic>).map((e) => e.toString()).toList();
});
final schematicTypesProvider = FutureProvider((ref) async {
final client = await ref.watch(sshClientProvider.future);
final result = await client
.run("grep ' Type: ' /configs/GameModes/*.yml")
.then((value) => String.fromCharCodes(value));
return result
.split("\n")
.where((element) => element.isNotEmpty)
.map((e) => e.split(": ")[2])
.toList();
return await ref.watch(httpClient.future).then((client) async {
final res = await client.get("/data/schematicTypes");
return (res.data as List<dynamic>)
.map((e) => SchematicType.fromJson(e))
.toList();
});
});
final mapsProvider = FutureProvider<Map<String, List<String>>>((ref) async {
final client = await ref.watch(sshClientProvider.future);
final gamemodes = await ref.read(fightServersProvider.future);
final maps = <String, List<String>>{};
for (var gm in gamemodes) {
final result = loadYaml(await client
.run("cat /configs/GameModes/$gm.yml")
.then((value) => String.fromCharCodes(value)));
final gmMaps = result?["Server"]?["Maps"] as YamlList?;
if (gmMaps != null) {
maps[gm] = gmMaps.toList().map((e) => e.toString()).toList();
}
final client = await ref.watch(httpClient.future);
final servers = await ref.watch(fightServersProvider.future);
final ret = <String, List<String>>{};
for (final server in servers) {
final res = await client.get("/data/gamemodes/$server/maps");
ret[server] = (res.data as List<dynamic>).map((e) => e.toString()).toList();
}
return maps;
return ret;
});
final usersProvider = FutureProvider((ref) async {
final client = await ref.watch(mysqlClientProvider.future);
final result = await client.execute("SELECT id, UserName FROM UserData");
return result.rows
.map((e) => User(e.typedColByName<int>("id")!, e.colByName("UserName")!))
.toList();
final client = await ref.watch(httpClient.future);
final res = await client.get("/data/users");
return (res.data as List<dynamic>).map((e) => User.fromJson(e)).toList();
});
class User {
@ -58,4 +39,19 @@ class User {
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,82 +1,23 @@
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
import 'package:hooks_riverpod/hooks_riverpod.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 prefs = await SharedPreferences.getInstance();
final username = prefs.getString("username")!;
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 key = prefs.getString("key")!;
final uneditablePastEvents = prefs.getBool("uneditablePastEvents") ?? false;
return UserData(
username: username,
method: method,
password: password,
privateKeyFile: privateKey,
sqlPassword: sqlPassword,
sqlUserName: sqlUsername,
useDevDB: useDevDB,
key: key,
uneditablePastEvents: uneditablePastEvents,
);
});
class UserData {
final String username;
final String? password;
final String? privateKeyFile;
final int method;
final String? sqlUserName;
final String? sqlPassword;
final bool useDevDB;
final String key;
final bool uneditablePastEvents;
UserData({
required this.username,
required this.password,
required this.privateKeyFile,
required this.method,
required this.sqlUserName,
required this.sqlPassword,
required this.useDevDB,
required this.key,
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_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../provider/events.dart';
import '../event.dart';
class EventDialog extends HookConsumerWidget {
const EventDialog({Key? key}) : super(key: key);
@ -68,11 +69,14 @@ class EventDialog extends HookConsumerWidget {
final event = await ref.read(eventRepositoryProvider.future).then(
(value) => value.createEvent(
eventName.text, startTime.value, endTime.value));
final eventFull = await ref
.read(eventRepositoryProvider.future)
.then((value) => value.getEvent(event.id));
nav.pop();
nav.push(
MaterialPageRoute(
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:hooks_riverpod/hooks_riverpod.dart';
import '../../provider/events.dart';
import '../../provider/user.dart';
import '../event.dart';
import 'error.dart';
import 'event_dialog.dart';
class EventListComponent extends HookConsumerWidget {
@ -84,20 +84,6 @@ class EventListComponent extends HookConsumerWidget {
],
);
}, 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);
}, loading: () {
return const Center(

Datei anzeigen

@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:dev_server_starter/src/screens/components/error.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@ -9,6 +8,7 @@ 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);

Datei anzeigen

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

Datei anzeigen

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

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_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../provider/events.dart';
import '../provider/server.dart';
import '../provider/user.dart';
import 'components/events_list.dart';
class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key);
@ -21,12 +19,12 @@ class HomeScreen extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
automaticallyImplyLeading: false,
actions: [
IconButton(
onPressed: servers.isLoading
? null
: () {
ref.refresh(serversProvider);
ref.refresh(eventsListProvider);
},
icon: const Icon(Icons.refresh),
@ -48,7 +46,7 @@ class HomeScreen extends HookConsumerWidget {
onPressed: () async {
final nav = Navigator.of(context);
final prefs = await SharedPreferences.getInstance();
await prefs.remove("username");
await prefs.remove("key");
ref.refresh(userDataProvider);
nav.pop();
nav.pushReplacementNamed("/");
@ -68,14 +66,14 @@ class HomeScreen extends HookConsumerWidget {
NavigationRail(
elevation: 1,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.dns),
label: Text('Server'),
),
NavigationRailDestination(
icon: Icon(Icons.calendar_today),
label: Text('Events'),
),
NavigationRailDestination(
icon: Icon(Icons.dns),
label: Text('Server'),
),
],
labelType: NavigationRailLabelType.selected,
trailing: IconButton(
@ -93,8 +91,9 @@ class HomeScreen extends HookConsumerWidget {
child: IndexedStack(
index: navRailIndex.value,
children: const [
ServerListComponent(),
//ServerListComponent(),
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_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../main.dart';
import '../provider/user.dart';
class LoginScreenWidget extends HookConsumerWidget {
const LoginScreenWidget({
@ -17,145 +12,60 @@ class LoginScreenWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final clientFunction = ref.watch(clientFunctionProvider);
final keyController = useTextEditingController();
final updater = useState(0);
useEffect(() {
clientFunction.whenData((clientFunction) {
Navigator.of(context).pushReplacementNamed("/home");
});
return null;
}, []);
ref.read(userDataProvider).when(
data: (data) {
keyController.text = data.key;
updater.value++;
},
loading: () {},
error: (error, stack) {},
);
return clientFunction.when(data: (data) {
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Center(
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
automaticallyImplyLeading: false,
),
body: Center(
child: SizedBox(
width: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Redirecting..."),
ElevatedButton(
onPressed: () {
Navigator.of(context).popAndPushNamed("/home");
},
child: const Text("Go to home"))
Text(
'Login',
style: Theme.of(context).textTheme.headline4,
),
TextField(
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 '../provider/events.dart';
import '../provider/types.dart';
class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
final List<String> types;
final RESET_TYPE = SchematicType("RESET", "RESET");
class SchematicTypeSearchDelegate extends SearchDelegate<SchematicType?> {
final List<SchematicType> types;
SchematicTypeSearchDelegate(this.types);
@ -42,7 +44,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
Widget _buildList(BuildContext context) {
final out = types
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
.where((element) => element.name.contains(query.toLowerCase()))
.toList();
return ListView.builder(
itemCount: out.length + 1,
@ -52,7 +54,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
title: const Text("Reset"),
leading: const Icon(Icons.clear),
onTap: () {
close(context, "reset");
close(context, RESET_TYPE);
},
);
}
@ -60,7 +62,7 @@ class SchematicTypeSearchDelegate extends SearchDelegate<String?> {
final type = out[index - 1];
return ListTile(
title: Text(type),
title: Text(type.name),
onTap: () {
close(context, type);
},
@ -244,8 +246,8 @@ class UserSearchDelegate extends SearchDelegate<User?> {
}
}
class TeamSearchDelegate extends SearchDelegate<ShortTeam?> {
final List<ShortTeam> teams;
class TeamSearchDelegate extends SearchDelegate<Team?> {
final List<Team> 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_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../main.dart';
import '../provider/user.dart';
import 'components/error.dart';
class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({Key? key}) : super(key: key);
@ -18,14 +15,7 @@ class SettingsScreen extends HookConsumerWidget {
return userDate.when(
data: (data) {
final userName = useTextEditingController(text: data.username);
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 keyController = useTextEditingController(text: data.key);
final uneditablePastEvents = useState(data.uneditablePastEvents);
final changed = useState(false);
@ -37,43 +27,16 @@ class SettingsScreen extends HookConsumerWidget {
IconButton(
onPressed: changed.value
? () async {
if (userName.text.isEmpty ||
(privateKeyFile.value == null &&
password.text.isEmpty)) {
if (keyController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Username and password or private key are required"),
content: Text("The Key must be Set"),
),
);
return;
}
final prefs = await SharedPreferences.getInstance();
await prefs.setString("username", userName.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.setString("key", keyController.text);
await prefs.setBool(
"uneditablePastEvents", uneditablePastEvents.value);
ref.refresh(userDataProvider);
@ -91,105 +54,17 @@ class SettingsScreen extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
TextField(
controller: userName,
controller: keyController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username',
labelText: 'Key',
),
onChanged: (value) {
changed.value = true;
},
),
const SizedBox(height: 10),
TextField(
controller: password,
obscureText: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
onChanged: (value) {
changed.value = true;
},
),
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(
title: const Text("Disable Past Events uneditable"),
value: uneditablePastEvents.value,

Datei anzeigen

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
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_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "SteamWar Dev Server");
gtk_window_set_title(window, "SteamWar Multitool");
}
gtk_window_set_default_size(window, 1280, 720);

Datei anzeigen

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

Datei anzeigen

@ -22,13 +22,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
async:
dependency: transitive
description:
@ -43,13 +36,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
buffer:
dependency: transitive
description:
name: buffer
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
build:
dependency: transitive
description:
@ -169,13 +155,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.4"
dartssh2:
dio:
dependency: "direct main"
description:
name: dartssh2
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.3"
version: "4.0.6"
equatable:
dependency: transitive
description:
@ -204,13 +190,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -237,13 +216,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -387,13 +359,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -402,33 +367,12 @@ packages:
source: hosted
version: "2.1.0"
path:
dependency: "direct main"
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -436,13 +380,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -457,13 +394,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
pinenacl:
dependency: transitive
description:
name: pinenacl
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1"
platform:
dependency: transitive
description:
@ -485,13 +415,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.2"
pool:
dependency: transitive
description:
@ -686,13 +609,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
typed_data:
dependency: transitive
description:
@ -743,7 +659,7 @@ packages:
source: hosted
version: "3.4.0"
yaml:
dependency: "direct main"
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"

Datei anzeigen

@ -1,4 +1,4 @@
name: dev_server_starter
name: steamwar_multitool
description: A new Flutter project.
authors:
- Chaoscaot
@ -14,17 +14,12 @@ dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dartssh2: ^2.7.3
flutter_hooks: ^0.18.5+1
hooks_riverpod: ^2.0.2
json_annotation: '>=4.7.0 <4.8.0'
path: ^1.8.2
path_provider: ^2.0.11
shared_preferences: ^2.0.15
file_picker: ^5.2.2
xterm: ^3.4.0
mysql_client: ^0.0.27
yaml: ^3.1.1
dio: ^4.0.6
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);
Win32Window::Point origin(10, 10);
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;
}
window.SetQuitOnClose(true);