diff --git a/lib/src/app.dart b/lib/src/app.dart index 3305fe7..8e1c67e 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,20 +1,39 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:steamwar_multitool/src/screens/event.dart'; import 'screens/home.dart'; import 'screens/login.dart'; import 'screens/userinfo.dart'; +final _routes = GoRouter( + routes: [ + GoRoute(path: "/", builder: (context, state) => const HomeScreen()), + GoRoute( + path: "/login", builder: (context, state) => const LoginScreenWidget()), + GoRoute( + path: "/settings", builder: (context, state) => const SettingsScreen()), + GoRoute( + path: "/event/:eventId", + builder: (context, state) { + final eventId = int.tryParse(state.params['eventId']!); + if (eventId == null) { + context.go("/"); + return const SizedBox(); + } + return EditEventScreen(eventId); + }), + ], + initialLocation: "/login", +); + class DevServerStarterApp extends StatelessWidget { const DevServerStarterApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return MaterialApp( - routes: { - "/": (context) => const LoginScreenWidget(), - "/home": (context) => const HomeScreen(), - "/settings": (context) => const SettingsScreen(), - }, + return MaterialApp.router( + routerConfig: _routes, title: 'Event-Tool', theme: ThemeData( useMaterial3: true, diff --git a/lib/src/provider/http.dart b/lib/src/provider/http.dart index 4d8ead0..c1e25c4 100644 --- a/lib/src/provider/http.dart +++ b/lib/src/provider/http.dart @@ -7,7 +7,7 @@ final serverUrlProvider = Provider((ref) { if (kDebugMode) { return "http://localhost:8000"; } else { - return "https://steamwar.de"; + return "http://localhost:8000"; } }); diff --git a/lib/src/screens/components/event_dialog.dart b/lib/src/screens/components/event_dialog.dart index 514cc09..2d1b8f2 100644 --- a/lib/src/screens/components/event_dialog.dart +++ b/lib/src/screens/components/event_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../provider/events.dart'; @@ -65,21 +66,13 @@ class EventDialog extends HookConsumerWidget { ), TextButton( onPressed: () async { - final nav = Navigator.of(context); 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, eventFull); - }, - ), - ); + context.go('/event/${event.id}'); }, child: const Text("Create"), ), diff --git a/lib/src/screens/components/events_list.dart b/lib/src/screens/components/events_list.dart index 67dfc76..914481e 100644 --- a/lib/src/screens/components/events_list.dart +++ b/lib/src/screens/components/events_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../provider/events.dart'; @@ -40,17 +41,7 @@ class EventListComponent extends HookConsumerWidget { subtitle: Text("${e.start} - ${e.end}"), leading: const Icon(Icons.event), onTap: () async { - final nav = Navigator.of(context); - final event = await ref - .read(eventRepositoryProvider.future) - .then((value) => value.getEvent(e.id)); - nav.push( - MaterialPageRoute( - builder: (context) { - return EditEventScreen(true, event); - }, - ), - ); + context.go("/event/${e.id}"); }, ), ), @@ -64,21 +55,7 @@ class EventListComponent extends HookConsumerWidget { leading: const Icon(Icons.check), subtitle: Text("${e.start} - ${e.end}"), onTap: () async { - final nav = Navigator.of(context); - final event = await ref - .read(eventRepositoryProvider.future) - .then((value) => value.getEvent(e.id)); - nav.push( - MaterialPageRoute( - builder: (context) { - return EditEventScreen( - false || - (userData.valueOrNull?.uneditablePastEvents ?? - false), - event); - }, - ), - ); + context.go("/event/${e.id}"); }, )), ], diff --git a/lib/src/screens/event.dart b/lib/src/screens/event.dart index 526c336..cd6d58a 100644 --- a/lib/src/screens/event.dart +++ b/lib/src/screens/event.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../provider/events.dart'; @@ -17,14 +18,57 @@ T? catchToNull(T Function() f) { } class EditEventScreen extends HookConsumerWidget { - final bool editable; + final int eventId; + const EditEventScreen(this.eventId, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final eventFuture = useMemoized(() => ref + .watch(eventRepositoryProvider.future) + .then((value) => value.getEvent(eventId))); + + return FutureBuilder( + future: eventFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Scaffold( + appBar: AppBar( + title: const Text('Error'), + ), + body: Center( + child: Text('Error: ${snapshot.error}'), + ), + ); + } + if (!snapshot.hasData) { + return Scaffold( + appBar: AppBar( + title: const Text('Loading'), + ), + body: const Center( + child: CircularProgressIndicator(), + ), + ); + } + final eventData = snapshot.data as EventExtended; + + return _EventScreen( + eventData: eventData, + ); + }, + ); + } +} + +class _EventScreen extends HookConsumerWidget { + const _EventScreen({Key? key, required this.eventData}) : super(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); @@ -41,10 +85,9 @@ class EditEventScreen extends HookConsumerWidget { final spectateSystemState = useState(event.spectateSystem); final changed = useState(false); - return Scaffold( appBar: AppBar( - title: Text("${editable ? "Edit" : "View"} ${event.name}"), + title: Text("${"Edit"} ${event.name}"), actions: [ IconButton( onPressed: () { @@ -95,7 +138,7 @@ class EditEventScreen extends HookConsumerWidget { )) .whenComplete( () => ref.invalidate(eventsListProvider)); - Navigator.of(context).pop(); + context.go('/'); } : null, icon: const Icon(Icons.save)), @@ -126,7 +169,7 @@ class EditEventScreen extends HookConsumerWidget { } else { canPop = true; } - if (canPop) Navigator.of(context).pop(); + if (canPop) context.go("/"); }, icon: const Icon(Icons.arrow_back), ), @@ -141,7 +184,6 @@ class EditEventScreen extends HookConsumerWidget { labelText: "Name", border: OutlineInputBorder(), ), - enabled: editable, onChanged: (value) { changed.value = true; }, @@ -153,7 +195,7 @@ class EditEventScreen extends HookConsumerWidget { DateTimeEditor((p0) { deadlineState.value = p0; changed.value = true; - }, deadlineState.value, editable), + }, deadlineState.value, true), ], ), const SizedBox(height: 8), @@ -163,7 +205,7 @@ class EditEventScreen extends HookConsumerWidget { DateTimeEditor((p0) { startDateState.value = p0; changed.value = true; - }, startDateState.value, editable), + }, startDateState.value, true), ], ), const SizedBox(height: 8), @@ -173,7 +215,7 @@ class EditEventScreen extends HookConsumerWidget { DateTimeEditor((p0) { endDateState.value = p0; changed.value = true; - }, endDateState.value, editable), + }, endDateState.value, true), ], ), const SizedBox(height: 8), @@ -185,7 +227,6 @@ class EditEventScreen extends HookConsumerWidget { errorText: invalidMaxTeamMembers.value ? "Must be a number above 0" : null), - enabled: editable, keyboardType: TextInputType.number, onChanged: (value) { if (value.isEmpty || @@ -199,44 +240,39 @@ class EditEventScreen extends HookConsumerWidget { } }, ), - if (editable) - Slider( - value: maxTeamMembersState.value.toDouble(), - onChanged: editable - ? (p0) { - maxTeamMembersState.value = p0.toInt(); - maxTeamMembersController.text = p0.toInt().toString(); - changed.value = true; - } - : null, - min: min(1, maxTeamMembersState.value.toDouble()), - max: max(30, maxTeamMembersState.value.toDouble()), - divisions: - max(30, maxTeamMembersState.value.toDouble()).toInt() - 1, - label: maxTeamMembersState.value.toString(), - ), + Slider( + value: maxTeamMembersState.value.toDouble(), + onChanged: (p0) { + maxTeamMembersState.value = p0.toInt(); + maxTeamMembersController.text = p0.toInt().toString(); + changed.value = true; + }, + min: min(1, maxTeamMembersState.value.toDouble()), + max: max(30, maxTeamMembersState.value.toDouble()), + divisions: + max(30, maxTeamMembersState.value.toDouble()).toInt() - 1, + label: maxTeamMembersState.value.toString(), + ), const SizedBox(height: 8), Row( children: [ const Text("Schematic Type: "), TextButton( - onPressed: editable - ? () async { - final types = - await ref.read(schematicTypesProvider.future); - final out = await showSearch( - context: context, - delegate: SchematicTypeSearchDelegate(types)); - if (out == RESET_TYPE) { - schematicTypeState.value = null; - changed.value = true; - } else { - schematicTypeState.value = - out ?? schematicTypeState.value; - changed.value = true; - } - } - : null, + onPressed: () async { + final types = + await ref.read(schematicTypesProvider.future); + final out = await showSearch( + context: context, + delegate: SchematicTypeSearchDelegate(types)); + if (out == RESET_TYPE) { + schematicTypeState.value = null; + changed.value = true; + } else { + schematicTypeState.value = + out ?? schematicTypeState.value; + changed.value = true; + } + }, child: Text(schematicTypeState.value?.name ?? "Select")), ], ), @@ -248,7 +284,6 @@ class EditEventScreen extends HookConsumerWidget { changed.value = true; }, title: const Text("Public Only"), - enabled: editable, ), const SizedBox(height: 8), CheckboxListTile( @@ -258,7 +293,6 @@ class EditEventScreen extends HookConsumerWidget { changed.value = true; }, title: const Text("Use Spectate System"), - enabled: editable, ), const SizedBox(height: 8), Text("Teams", style: Theme.of(context).textTheme.headline6), diff --git a/lib/src/screens/home.dart b/lib/src/screens/home.dart index e357442..5c4ca4b 100644 --- a/lib/src/screens/home.dart +++ b/lib/src/screens/home.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -15,6 +18,23 @@ class HomeScreen extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final servers = ref.watch(serversProvider); + final userData = ref.watch(userDataProvider); + + if (userData.hasError) { + Timer.run(() { + context.go('/login'); + }); + return Scaffold( + body: Center( + child: FloatingActionButton.large( + onPressed: () { + context.go('/login'); + }, + child: const Icon(Icons.login)), + ), + ); + } + final navRailIndex = useState(0); return Scaffold( appBar: AppBar( @@ -49,7 +69,7 @@ class HomeScreen extends HookConsumerWidget { await prefs.remove("key"); ref.refresh(userDataProvider); nav.pop(); - nav.pushReplacementNamed("/"); + context.go('/login'); }, child: const Text('Logout', style: TextStyle(color: Colors.red)), diff --git a/lib/src/screens/login.dart b/lib/src/screens/login.dart index b9b4ad9..a40c8a3 100644 --- a/lib/src/screens/login.dart +++ b/lib/src/screens/login.dart @@ -1,7 +1,12 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:steamwar_multitool/src/provider/http.dart'; import '../provider/user.dart'; @@ -15,14 +20,22 @@ class LoginScreenWidget extends HookConsumerWidget { final keyController = useTextEditingController(); final updater = useState(0); - ref.read(userDataProvider).when( - data: (data) { - keyController.text = data.key; - updater.value++; - }, - loading: () {}, - error: (error, stack) {}, - ); + final userData = ref.watch(userDataProvider); + + if (userData.hasValue) { + Timer.run(() { + context.go('/'); + }); + return Scaffold( + body: Center( + child: FloatingActionButton.large( + onPressed: () { + context.go('/'); + }, + child: const Icon(Icons.home)), + ), + ); + } return Scaffold( appBar: AppBar( @@ -53,11 +66,28 @@ class LoginScreenWidget extends HookConsumerWidget { 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"); + final dio = Dio( + BaseOptions( + baseUrl: ref.read(serverUrlProvider), + headers: { + 'X-SW-Auth': keyController.text, + }, + contentType: 'application/json', + ), + ); + try { + await dio.get('/data'); + final prefs = await SharedPreferences.getInstance(); + await prefs.setString("key", keyController.text); + ref.invalidate(userDataProvider); + context.go('/'); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Invalid Key'), + ), + ); + } } : null, child: const Icon(Icons.forward), diff --git a/pubspec.lock b/pubspec.lock index 1fece27..3f35b37 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -261,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + go_router: + dependency: "direct main" + description: + name: go_router + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" graphs: dependency: transitive description: @@ -667,4 +674,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.1 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index fa1cdb7..e827707 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: shared_preferences: ^2.0.15 xterm: ^3.4.0 dio: ^4.0.6 + go_router: ^6.0.0 dev_dependencies: diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..29d4053 100644 Binary files a/web/favicon.png and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..385ce74 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..31c3dbf 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d7..2c72b1f 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c566..bb7034e 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ