Finishied room member screen
The screen allows every room member, to view a list of other room members, and their role (Owner, Admin or Member). If the user is allowed (Owner, Admin, changeAdmin/manageMembers), to either kick or promote members to the admin role, the list tile will have a tap event. NOTE: This is why some members do not have a hover animation. For example the owner cannot be kicked.
This commit is contained in:
parent
ecd87cf2cd
commit
6b98c696ea
4 changed files with 378 additions and 12 deletions
|
@ -320,7 +320,13 @@ class RoomMember {
|
|||
|
||||
factory RoomMember.fromJSON(dynamic json) {
|
||||
return RoomMember(
|
||||
id: json['name'], serverTag: json['server'], isAdmin: json['admin']);
|
||||
id: json['name'],
|
||||
serverTag: json['server'],
|
||||
isAdmin: json['admin'] == 1);
|
||||
}
|
||||
|
||||
String get humanReadableName {
|
||||
return '$id@$serverTag';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/screens/room/edit.dart';
|
||||
import 'package:outbag_app/screens/room/join.dart';
|
||||
import 'package:outbag_app/screens/room/members.dart';
|
||||
import 'package:outbag_app/screens/room/new.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -38,6 +39,13 @@ final routesLoggedIn = RouteMap(routes: {
|
|||
|
||||
return MaterialPage(child: EditRoomPage(server, tag));
|
||||
},
|
||||
'/r/:server/:tag/members': (info) {
|
||||
final server = info.pathParameters['server'] ?? "";
|
||||
final tag = info.pathParameters['tag'] ?? "";
|
||||
|
||||
return MaterialPage(child: ManageRoomMembersPage(server, tag));
|
||||
},
|
||||
|
||||
}, onUnknownRoute: (_) => const Redirect('/'));
|
||||
|
||||
void main() {
|
||||
|
|
346
lib/screens/room/members.dart
Normal file
346
lib/screens/room/members.dart
Normal file
|
@ -0,0 +1,346 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:outbag_app/backend/permissions.dart';
|
||||
import 'package:outbag_app/backend/request.dart';
|
||||
import 'package:outbag_app/backend/room.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:routemaster/routemaster.dart';
|
||||
|
||||
class ManageRoomMembersPage extends StatefulWidget {
|
||||
final String server;
|
||||
final String tag;
|
||||
|
||||
const ManageRoomMembersPage(this.server, this.tag, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ManageRoomMembersPageState();
|
||||
}
|
||||
|
||||
class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
||||
List<RoomMember> list = [];
|
||||
RoomInfo? info;
|
||||
|
||||
void fetchUserInfo() {
|
||||
final rmaster = Routemaster.of(context);
|
||||
final sm = ScaffoldMessenger.of(context);
|
||||
|
||||
doNetworkRequest(
|
||||
sm,
|
||||
req: (user) => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user!,
|
||||
target: (user.server),
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
rmaster.replace('/');
|
||||
return false;
|
||||
},
|
||||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
setState(() {
|
||||
this.info = info;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void fetchMembers() {
|
||||
final rmaster = Routemaster.of(context);
|
||||
final sm = ScaffoldMessenger.of(context);
|
||||
|
||||
doNetworkRequest(sm,
|
||||
req: (user) => postWithCreadentials(
|
||||
credentials: user!,
|
||||
target: user.server,
|
||||
path: 'getRoomMembers',
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
rmaster.replace('/');
|
||||
return false;
|
||||
},
|
||||
onOK: (body) {
|
||||
final List<RoomMember> list = body['data'].map<RoomMember>((json) {
|
||||
return RoomMember.fromJSON(json);
|
||||
}).toList();
|
||||
|
||||
setState(() {
|
||||
this.list = list;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchUserInfo();
|
||||
fetchMembers();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Room Members (${list.length})'),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
// go back
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: "Go back",
|
||||
),
|
||||
//actions: [
|
||||
// // NOTE: Maybe add a search icon
|
||||
// // and general search functionality here
|
||||
//],
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = list[index];
|
||||
|
||||
String role = "Member";
|
||||
if (info != null &&
|
||||
(info?.owner)! == item.id &&
|
||||
widget.server == item.serverTag) {
|
||||
role = "Owner";
|
||||
} else if (item.isAdmin) {
|
||||
role = "Admin";
|
||||
}
|
||||
|
||||
bool enable = true;
|
||||
// perform permission check
|
||||
if (info == null ||
|
||||
!((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! & oB("1100000") != 0))) {
|
||||
// NOTE: do not show error message
|
||||
// user should assume,
|
||||
// that it wasn't even possible
|
||||
// to click on ListTile
|
||||
enable = false;
|
||||
} else if (info != null &&
|
||||
item.id == info?.owner &&
|
||||
widget.server == item.serverTag) {
|
||||
// cannot kick admin
|
||||
enable = false;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(item.humanReadableName),
|
||||
subtitle: Text(role),
|
||||
leading: const Icon(Icons.person),
|
||||
onTap: !enable?null:() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => BottomSheet(
|
||||
onClosing: () {},
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(item.humanReadableName,
|
||||
style: textTheme.displaySmall)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
...((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission.changeAdmin !=
|
||||
0))
|
||||
? [
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? 'Remove admin privileges'
|
||||
: 'Make Admin'),
|
||||
subtitle: Text(item.isAdmin
|
||||
? 'Revokes admin privileges from the user'
|
||||
: 'Grants the user the permission to do everything'),
|
||||
onTap: () {
|
||||
// make user admin
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
icon: const Icon(Icons
|
||||
.supervisor_account),
|
||||
title: Text(item
|
||||
.isAdmin
|
||||
? 'Remove admin privileges'
|
||||
: 'Make Admin'),
|
||||
content: Text(item
|
||||
.isAdmin
|
||||
? "Do you really want to remove ${item.humanReadableName}'s admin privileges"
|
||||
: 'Do you really want to make ${item.humanReadableName} admin?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
// NOTE: cancel only closes the dialog
|
||||
// whilst OK closes both
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
() async {
|
||||
// send request
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(
|
||||
context);
|
||||
final nav =
|
||||
Navigator.of(
|
||||
context);
|
||||
|
||||
doNetworkRequest(
|
||||
scaffMgr,
|
||||
req: (user) =>
|
||||
postWithCreadentials(
|
||||
path: 'setAdminStatus',
|
||||
credentials: user!,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'roomServer': widget.server,
|
||||
'server': item.serverTag,
|
||||
'name': item.id,
|
||||
'admin': !item.isAdmin
|
||||
}),
|
||||
onOK: (_) {
|
||||
fetchMembers();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child:
|
||||
const Text(
|
||||
'OK'),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
...((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission
|
||||
.manageMembers !=
|
||||
0))
|
||||
? [
|
||||
ListTile(
|
||||
leading:
|
||||
const Icon(Icons.person_remove),
|
||||
title: const Text('Kick User'),
|
||||
subtitle: const Text(
|
||||
"Temporarrily remove user from server (they'll be able to join the room again)"),
|
||||
onTap: () {
|
||||
// remove user from room
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
icon: const Icon(Icons
|
||||
.person_remove),
|
||||
title: const Text(
|
||||
'Kick User'),
|
||||
content: Text(
|
||||
'Do you really want to kick ${item.humanReadableName}?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
// NOTE: cancel only closes the dialog
|
||||
// whilst OK closes both
|
||||
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
() async {
|
||||
// send request
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(
|
||||
context);
|
||||
final nav =
|
||||
Navigator.of(
|
||||
context);
|
||||
|
||||
doNetworkRequest(
|
||||
scaffMgr,
|
||||
req: (user) =>
|
||||
postWithCreadentials(
|
||||
path: 'kickMember',
|
||||
credentials: user!,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'roomServer': widget.server,
|
||||
'name': item.id,
|
||||
'server': item.serverTag
|
||||
}),
|
||||
onOK: (_) {
|
||||
fetchMembers();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text(
|
||||
'Kick User'),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
child: const Text('Close'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: list.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
bool Function()? onNetworkErr,
|
||||
bool Function()? onUnknownErr,
|
||||
bool Function()? onUserErr,
|
||||
Function()? onAnyErr,
|
||||
Function()? after,
|
||||
bool Function(Map<String, dynamic>)? onServerErr,
|
||||
bool needUser = true}) async {
|
||||
|
@ -35,6 +36,9 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
if (onAnyErr != null) {
|
||||
onAnyErr();
|
||||
}
|
||||
if (after != null) {
|
||||
after();
|
||||
}
|
||||
|
@ -56,7 +60,9 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
if (showBar && sm != null) {
|
||||
showSimpleSnackbar(sm, text: 'Network Error', action: 'Dismiss');
|
||||
}
|
||||
|
||||
if (onAnyErr != null) {
|
||||
onAnyErr();
|
||||
}
|
||||
if (after != null) {
|
||||
after();
|
||||
}
|
||||
|
@ -75,16 +81,16 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
if (onServerErr != null) {
|
||||
showBar = onServerErr(res.body);
|
||||
}
|
||||
|
||||
if (showBar && sm != null) {
|
||||
showSimpleSnackbar(sm, text: errorAsString(res.body), action: 'OK');
|
||||
}
|
||||
if (onAnyErr != null) {
|
||||
onAnyErr();
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (_) {
|
||||
bool showBar = true;
|
||||
|
||||
print(e);
|
||||
|
||||
if (onUnknownErr != null) {
|
||||
showBar = onUnknownErr();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue