From 4775d6923e12dd359ac8e18f57d988100b5f41e9 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Fri, 24 Mar 2023 13:22:48 +0100 Subject: [PATCH] Added edit room screen Data-fetch routine: fetch room information from server -> if unsuccessfull load room from disk The room will automatically be closed, if either of the following conditions apply: - the user is offline and no data was found on disk - the network request returned a server error --- lib/main.dart | 8 + lib/screens/room/edit.dart | 338 ++++++++++++++++++++++++++++++ lib/screens/room/pages/about.dart | 23 +- pubspec.lock | 8 - pubspec.yaml | 5 - 5 files changed, 356 insertions(+), 26 deletions(-) create mode 100644 lib/screens/room/edit.dart diff --git a/lib/main.dart b/lib/main.dart index 6104cae..c9de44f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:outbag_app/backend/user.dart'; +import 'package:outbag_app/screens/room/edit.dart'; import 'package:outbag_app/screens/room/join.dart'; import 'package:outbag_app/screens/room/new.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart'; @@ -31,6 +32,13 @@ final routesLoggedIn = RouteMap(routes: { return MaterialPage(child: RoomPage(server, tag)); }, + '/r/:server/:tag/edit': (info) { + final server = info.pathParameters['server'] ?? ""; + final tag = info.pathParameters['tag'] ?? ""; + + return MaterialPage(child: EditRoomPage(server, tag)); + }, + }, onUnknownRoute: (_) => const Redirect('/')); void main() { diff --git a/lib/screens/room/edit.dart b/lib/screens/room/edit.dart new file mode 100644 index 0000000..3f52f6b --- /dev/null +++ b/lib/screens/room/edit.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:outbag_app/backend/errors.dart'; +import 'package:outbag_app/backend/request.dart'; +import 'package:outbag_app/backend/room.dart'; +import 'package:outbag_app/backend/user.dart'; +import 'package:outbag_app/tools/fetch_wrapper.dart'; +import 'package:routemaster/routemaster.dart'; +import 'dart:math'; + +class EditRoomPage extends StatefulWidget { + final String server; + final String tag; + + const EditRoomPage(this.server, this.tag, {super.key}); + + @override + State createState() => _EditRoomPageState(); +} + +class _EditRoomPageState extends State { + final TextEditingController _ctrName = TextEditingController(); + final TextEditingController _ctrID = TextEditingController(); + final TextEditingController _ctrDescription = TextEditingController(); + RoomIcon _ctrIcon = RoomIcon.other; + Room? room; + + // show spinner by default + // until data has been fetched + bool showSpinner = true; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + fetchInfo(); + } + + void initFromRoom(Room room) { + _ctrID.text = room.id; + _ctrName.text = room.name; + _ctrDescription.text = room.description; + _ctrIcon = room.icon!; + + setState(() { + this.room = room; + }); + } + + // fetch room information + void fetchInfo() async { + final sm = ScaffoldMessenger.of(context); + final rmaster = Routemaster.of(context); + + doNetworkRequest( + sm, + req: (user) => postWithCreadentials( + path: 'getRoomInfo', + credentials: user!, + target: (user.server), + body: {'room': widget.tag, 'server': widget.server}), + onOK: (body) async { + final room = Room.fromJSON(body['data']); + room.toDisk(); + }, + onNetworkErr: () { + // no room data available + // use data from disk + (() async { + try { + final diskRoom = + await Room.fromDisk(serverTag: widget.server, id: widget.tag); + initFromRoom(diskRoom); + } catch (_) { + // no room data available + // close screen + rmaster.replace('/'); + } + })(); + return true; + }, + onServerErr: (json) { + // user not allowed to be here + // close screen + rmaster.replace('/'); + return true; + }, + ); + } + + @override + void initState() { + super.initState(); + + Room.listen((_) async { + // rooms changed on disk + // probably this one, + // because it is currently open + // NOTE: might be a different room + // (if a background listener is implemented at some point, + // checking if this room changed might improve performance) + try { + final room = + await Room.fromDisk(serverTag: widget.server, id: widget.tag); + + initFromRoom(room); + + setState(() { + showSpinner = false; + }); + } catch (_) {} + }); + } + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context) + .textTheme + .apply(displayColor: Theme.of(context).colorScheme.onSurface); + + double width = MediaQuery.of(context).size.width; + double height = MediaQuery.of(context).size.height; + double smallest = min(min(width, height), 400); + + return showSpinner + ? Scaffold( + body: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + Text('Loading', style: textTheme.titleLarge), + ]))) + : Scaffold( + appBar: AppBar( + title: const Text('Edit Room'), + leading: IconButton( + onPressed: () { + // go back + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.arrow_back), + tooltip: "Go back", + ), + ), + body: Center( + child: Flexible( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + icon: SvgPicture.asset( + _ctrIcon.img, + width: smallest * 0.3, + height: smallest * 0.3, + ), + tooltip: 'Change room icon', + onPressed: () { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: + const Text('Choose a room Icon'), + actions: const [], + content: SizedBox( + width: smallest * 0.3 * 3, + height: smallest * 0.3 * 3, + child: GridView.count( + crossAxisCount: 3, + children: RoomIcon.list() + .map((icon) { + return GridTile( + child: IconButton( + icon: SvgPicture + .asset( + icon.img, + width: smallest * + 0.3, + height: smallest * + 0.3, + ), + tooltip: icon.text, + onPressed: () { + setState(() { + _ctrIcon = icon; + }); + Navigator.of( + context) + .pop(); + })); + }).toList())), + )); + }, + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + enabled: false, + controller: _ctrID, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.fact_check), + labelText: 'Room ID', + hintText: 'Unique room id', + helperText: + 'the room id and server tag allow the room to be identified', + border: OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _ctrName, + keyboardType: TextInputType.name, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.badge), + labelText: 'Room Name', + hintText: 'Give your room a name', + helperText: + 'Easily identify a room with a human readable name', + border: OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _ctrDescription, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.dns), + labelText: 'Room Description', + hintText: 'Briefly describe your Room', + helperText: + 'Make it easier for other to know what this room is used for', + border: OutlineInputBorder(), + ), + ), + ), + ], + )))), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + final scaffMgr = ScaffoldMessenger.of(context); + final nav = Navigator.of(context); + + // name may not be empty + if (_ctrName.text.isEmpty) { + final snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + content: const Text('Please specify a room name'), + action: SnackBarAction( + label: 'Ok', + onPressed: () { + scaffMgr.hideCurrentSnackBar(); + }, + ), + ); + + scaffMgr.hideCurrentSnackBar(); + scaffMgr.showSnackBar(snackBar); + + return; + } + + User user; + try { + user = await User.fromDisk(); + } catch (_) { + // user data invalid + // shouldn't happen + return; + } + + Room clone = room!; + clone.name = _ctrName.text; + clone.description = _ctrDescription.text; + clone.icon = _ctrIcon; + + try { + final resp = await postWithCreadentials( + target: user.server, + credentials: user, + path: 'changeRoomMeta', + body: { + 'room': clone.id, + 'server': clone.serverTag, + 'title': clone.name, + 'description': clone.description, + 'icon': clone.icon?.type, + }); + if (resp.res == Result.ok) { + // room was created + // save room + await clone.toDisk(); + nav.pop(); + } else { + // error + final snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + content: Text(errorAsString(resp.body)), + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + scaffMgr.hideCurrentSnackBar(); + }, + ), + ); + + scaffMgr.hideCurrentSnackBar(); + scaffMgr.showSnackBar(snackBar); + } + } catch (_) { + final snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + content: const Text('Network error'), + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + scaffMgr.hideCurrentSnackBar(); + }, + ), + ); + + scaffMgr.hideCurrentSnackBar(); + scaffMgr.showSnackBar(snackBar); + } + }, + label: const Text('Update'), + icon: const Icon(Icons.edit)), + ); + } +} diff --git a/lib/screens/room/pages/about.dart b/lib/screens/room/pages/about.dart index 1dcb544..53b4781 100644 --- a/lib/screens/room/pages/about.dart +++ b/lib/screens/room/pages/about.dart @@ -159,19 +159,16 @@ class _AboutRoomPageState extends State { RoomPermission.changeMeta != 0))) ? [ - ListTile( - trailing: const Icon(Icons.chevron_right), - title: const Text('Edit Metadata'), - subtitle: const Text( - 'Change the rooms name, description and icon'), - onTap: () { - // show edit room screen - showDialog( - context: context, - builder: (context) => Dialog.fullscreen( - child: EditRoomPage(widget.room!), - )); - }, + ListTile( + trailing: const Icon(Icons.chevron_right), + title: const Text('Edit Metadata'), + subtitle: const Text( + 'Change the rooms name, description and icon'), + onTap: () { + // show edit room screen + Routemaster.of(context).push( + '/r/${widget.room?.serverTag}/${widget.room?.id}/edit'); + }, ), ] : [], diff --git a/pubspec.lock b/pubspec.lock index 66a59f9..01d40d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be - url: "https://pub.dev" - source: hosted - version: "1.0.5" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bd0748d..b7b4b04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,11 +31,6 @@ dependencies: flutter: sdk: flutter routemaster: ^1.0.1 - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 flutter_svg: ^2.0.4 http: ^0.13.5 localstore: ^1.3.5