import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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:provider/provider.dart'; import 'package:routemaster/routemaster.dart'; import 'dart:math'; class JoinRoomPage extends StatefulWidget { const JoinRoomPage({super.key}); @override State createState() => _JoinRoomPageState(); } class _JoinRoomPageState extends State { List rooms = []; void fetchRooms() { final sm = ScaffoldMessenger.of(context); final user = context.read(); doNetworkRequest(null, req: () => postWithCreadentials( path: 'listPublicRooms', credentials: user, target: user.server, body: {}), onOK: (body) async { // parse rooms final list = 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 List blacklist = []; doNetworkRequest(sm, req: () => postWithCreadentials( path: 'listRooms', credentials: user, target: user.server, body: {}), onOK: (body) { final List list = body['data'].map((json) { return Room.fromJSON(json); }).toList(); for (Room r in list) { blacklist.add(r); } }); // process the list of public rooms final 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 (r.compareTo(room) == 0) { // server on white list // move to next iteration on outer for loop continue processor; } } builder.add(room); } catch (_) { // ignore room } } setState(() { rooms = builder; }); }); } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => fetchRooms()); } @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'), leading: IconButton( onPressed: () { // go back Routemaster.of(context).history.back(); }, 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 didChangeDependencies(); }, ), 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: 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]; return Card( margin: const EdgeInsets.all(8.0), clipBehavior: Clip.antiAliasWithSaveLayer, semanticContainer: true, child: InkWell( onTap: () { // 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); final user = context.read(); doNetworkRequest(scaffMgr, req: () => postWithCreadentials( credentials: user, target: user .server, path: 'joinPublicRoom', body: { 'room': room.id, 'server': room .serverTag }), onOK: (body) async { await room.toDisk(); nav.pop(); rmaster.replace( '/r/${room.serverTag}/${room.id}'); }); }, )) ]) ], ); }, ); }); }, child: Container( padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), child: ListTile( title: Text(room.name), visualDensity: const VisualDensity(vertical: 3), subtitle: Text(room.description), leading: AspectRatio( aspectRatio: 1 / 1, child: SvgPicture.asset("${room.icon?.img}"), ), hoverColor: Colors.transparent, )))); }, ), floatingActionButton: FloatingActionButton.extended( label: const Text('New'), icon: const Icon(Icons.add), onPressed: () { // create new room Routemaster.of(context).push("/add-room/new"); }, tooltip: 'Create Room', ), ); } }