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; 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() { 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 (_) {} }); WidgetsBinding.instance.addPostFrameCallback((_) => fetchInfo()); } @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)), ); } }