diff --git a/lib/screens/room/join.dart b/lib/screens/room/join.dart index 2e01c1f..57d2314 100644 --- a/lib/screens/room/join.dart +++ b/lib/screens/room/join.dart @@ -1,9 +1,11 @@ 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:routemaster/routemaster.dart'; +import 'dart:math'; class JoinRoomPage extends StatefulWidget { const JoinRoomPage({super.key}); @@ -15,45 +17,97 @@ class JoinRoomPage extends StatefulWidget { class _JoinRoomPageState extends State { List rooms = []; - @override - void initState() { - super.initState(); - (() async { - User user; - try { - user = await User.fromDisk(); - } catch (_) { - return; - } + void fetchData() async { + User user; + try { + user = await User.fromDisk(); + } catch (_) { + return; + } + try { + final resp = await postWithCreadentials( + path: 'listPublicRooms', + credentials: user, + target: user.server, + body: {}); + if (resp.res == Result.ok) { + // parse rooms + final list = resp.body['data']; + + // try to fetch a list of rooms the user is a member of + // use an empty blacklist when request is not successful + final blacklist = []; try { final resp = await postWithCreadentials( - path: 'listPublicRooms', + path: 'listRooms', credentials: user, target: user.server, body: {}); if (resp.res == Result.ok) { - // parse rooms final List list = resp.body['data'].map((json) { return Room.fromJSON(json); }).toList(); - setState(() { - rooms = list; - }); - } else { - throw Error(); + for (Room r in list) { + blacklist.add(r); + } + } + } catch (_) {} + + // process the list of public rooms + List builder = []; + processor: + for (dynamic raw in list) { + try { + final room = Room.fromJSON(raw); + + // figure out if room is on blacklist + // only add room to list, + // if not on blacklist + for (Room r in blacklist) { + if (room.serverTag == r.serverTag && room.id == r.id) { + // server on white list + // move to next iteration on outer for loop + continue processor; + } + } + + builder.add(room); + } catch (_) { + // ignore room } - } catch (_) { - // network error - // unable to load room list - // NOTE: might want to show snackbar - // with warning } - })(); + builder.sort(); + setState(() { + rooms = builder; + }); + } else { + throw Error(); + } + } catch (_) { + // network error + // unable to load room list + // NOTE: might want to show snackbar + // with warning + } + } + + @override + void initState() { + super.initState(); + fetchData(); } @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(width, height); + return Scaffold( appBar: AppBar( title: const Text('Join Room'), @@ -65,8 +119,57 @@ class _JoinRoomPageState extends State { icon: const Icon(Icons.arrow_back), tooltip: "Go back", ), + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: "Search", + onPressed: () { + // show searchbar + // NOTE: location currently unknown + }, + ), + IconButton( + icon: const Icon(Icons.refresh), + tooltip: "Refresh", + onPressed: () { + // fetch public rooms again + fetchData(); + }, + ), + MenuAnchor( + builder: (ctx, controller, child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(Icons.drafts), + child: const Text('Join invite-only room'), + onPressed: () { + // show settings screen + Routemaster.of(context).push("/add-room/by-id"); + }), + ]) + ], ), - body: ListView.builder( + body: rooms.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('No new Rooms found', style: textTheme.titleLarge), + ], + )) + : ListView.builder( itemCount: rooms.length, itemBuilder: (ctx, i) { final room = rooms[i]; @@ -79,6 +182,177 @@ class _JoinRoomPageState extends State { // TODO: show modalBottomSheet // with room information // and join button + showModalBottomSheet( + context: ctx, + builder: (ctx) { + return BottomSheet( + onClosing: () {}, + builder: (ctx) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(14), + child: Column(children: [ + // room icon + SvgPicture.asset( + (room.icon?.img)!, + width: smallest * 0.2, + height: smallest * 0.2, + ), + // room name + Text( + room.name, + style: textTheme.displayMedium, + ), + Text( + '${room.id}@${room.serverTag}', + style: textTheme.labelSmall, + ), + // description + Text(room.description, + style: textTheme.bodyLarge), + // visibility + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon(room.visibility?.icon), + Text((room + .visibility?.text)!), + ]), + ])), + // action buttons + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + // cancel button + Padding( + padding: + const EdgeInsets.all(14), + child: ElevatedButton.icon( + icon: + const Icon(Icons.close), + label: const Text('Cancel'), + onPressed: () { + // close sheet + Navigator.pop(context); + }, + )), + // join room button + Padding( + padding: + const EdgeInsets.all(14), + child: FilledButton.icon( + icon: + const Icon(Icons.check), + label: const Text('Join'), + onPressed: () async { + final scaffMgr = + ScaffoldMessenger.of( + context); + final rmaster = + Routemaster.of( + context); + final nav = + Navigator.of(context); + + // join room & close screen + User user; + try { + user = await User + .fromDisk(); + } catch (_) { + // user data invalid + // NOTE: shouldn't happen + // because the main.dart watches the auth meta data + // and auto logs-out the user + return; + } + + try { + final resp = + await postWithCreadentials( + target: + user.server, + path: + 'joinPublicRoom', + body: { + 'room': + room.id, + 'server': room + .serverTag + }, + credentials: + user); + if (resp.res == + Result.ok) { + // successfully joined room + await room.toDisk(); + nav.pop(); + rmaster.replace( + '/r/${room.serverTag}/${room.id}'); + } else { + // server 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 (_) { + // network error + final snackBar = + SnackBar( + behavior: + SnackBarBehavior + .floating, + content: const Text( + 'Network error'), + action: + SnackBarAction( + label: 'Dismiss', + onPressed: () { + scaffMgr + .hideCurrentSnackBar(); + }, + ), + ); + + scaffMgr + .hideCurrentSnackBar(); + scaffMgr.showSnackBar( + snackBar); + } + }, + )) + ]) + ], + ); + }, + ); + }); }, child: Container( padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),