import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.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:flutter_gen/gen_l10n/app_localizations.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 = []; await 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 == room) { // 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'), actions: [ IconButton( icon: const Icon(Icons.search), tooltip: AppLocalizations.of(context)!.search, onPressed: () { // show searchbar // NOTE: location currently unknown }, ), IconButton( icon: const Icon(Icons.refresh), tooltip: AppLocalizations.of(context)!.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: Text(AppLocalizations.of(context)!.joinRoomInvite), onPressed: () { // show settings screen context.goNamed('join-room-ota'); }), ]) ], ), body: rooms.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(AppLocalizations.of(context)!.noNewRoomsFound, 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: () { // 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(context))!), ]), ])), // action buttons Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // cancel button Padding( padding: const EdgeInsets.all(14), child: ElevatedButton.icon( icon: const Icon(Icons.close), label: Text(AppLocalizations.of(context)!.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: Text(AppLocalizations.of(context)!.joinRoom), onPressed: () async { final scaffMgr = ScaffoldMessenger.of( context); final nav = Navigator.of(context); final user = context.read(); final router = GoRouter.of(context); 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(); router .pushReplacementNamed( 'room', params: { 'server': room .serverTag, 'id': 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: Text(AppLocalizations.of(context)!.newRoom), icon: const Icon(Icons.add), onPressed: () { // create new room context.goNamed('new-room'); }, tooltip: AppLocalizations.of(context)!.createRoomShort, ), ); } }