Finished join-public-room screen

NOTE: If the user is already in a public room,
the room will not be shown in the list.

NOTE: The search funtionality
has not been implemented yet.

NOTE: The join invite-only room screen
will be part of a later commit.
But the endpoint has already been set to
/add-room/by-id
This commit is contained in:
Jakob Meier 2023-03-21 20:24:48 +01:00
parent 596c8cc4eb
commit 6347476b2f
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152

View file

@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import 'dart:math';
class JoinRoomPage extends StatefulWidget { class JoinRoomPage extends StatefulWidget {
const JoinRoomPage({super.key}); const JoinRoomPage({super.key});
@ -15,10 +17,7 @@ class JoinRoomPage extends StatefulWidget {
class _JoinRoomPageState extends State { class _JoinRoomPageState extends State {
List<Room> rooms = []; List<Room> rooms = [];
@override void fetchData() async {
void initState() {
super.initState();
(() async {
User user; User user;
try { try {
user = await User.fromDisk(); user = await User.fromDisk();
@ -34,11 +33,53 @@ class _JoinRoomPageState extends State {
body: {}); body: {});
if (resp.res == Result.ok) { if (resp.res == Result.ok) {
// parse rooms // 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: 'listRooms',
credentials: user,
target: user.server,
body: {});
if (resp.res == Result.ok) {
final List<Room> list = resp.body['data'].map<Room>((json) { final List<Room> list = resp.body['data'].map<Room>((json) {
return Room.fromJSON(json); return Room.fromJSON(json);
}).toList(); }).toList();
for (Room r in list) {
blacklist.add(r);
}
}
} catch (_) {}
// process the list of public rooms
List<Room> 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
}
}
builder.sort();
setState(() { setState(() {
rooms = list; rooms = builder;
}); });
} else { } else {
throw Error(); throw Error();
@ -49,11 +90,24 @@ class _JoinRoomPageState extends State {
// NOTE: might want to show snackbar // NOTE: might want to show snackbar
// with warning // with warning
} }
})(); }
@override
void initState() {
super.initState();
fetchData();
} }
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Join Room'), title: const Text('Join Room'),
@ -65,8 +119,57 @@ class _JoinRoomPageState extends State {
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
tooltip: "Go back", tooltip: "Go back",
), ),
actions: [
IconButton(
icon: const Icon(Icons.search),
tooltip: "Search",
onPressed: () {
// show searchbar
// NOTE: location currently unknown
},
), ),
body: ListView.builder( 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: 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, itemCount: rooms.length,
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final room = rooms[i]; final room = rooms[i];
@ -79,6 +182,177 @@ class _JoinRoomPageState extends State {
// TODO: show modalBottomSheet // TODO: show modalBottomSheet
// with room information // with room information
// and join button // 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( child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),