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) {
|
factory RoomMember.fromJSON(dynamic json) {
|
||||||
return RoomMember(
|
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/backend/user.dart';
|
||||||
import 'package:outbag_app/screens/room/edit.dart';
|
import 'package:outbag_app/screens/room/edit.dart';
|
||||||
import 'package:outbag_app/screens/room/join.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/screens/room/new.dart';
|
||||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -38,6 +39,13 @@ final routesLoggedIn = RouteMap(routes: {
|
||||||
|
|
||||||
return MaterialPage(child: EditRoomPage(server, tag));
|
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('/'));
|
}, onUnknownRoute: (_) => const Redirect('/'));
|
||||||
|
|
||||||
void main() {
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,12 @@ import 'package:outbag_app/backend/user.dart';
|
||||||
import 'package:outbag_app/tools/snackbar.dart';
|
import 'package:outbag_app/tools/snackbar.dart';
|
||||||
|
|
||||||
void doNetworkRequest(ScaffoldMessengerState? sm,
|
void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
{required Future<Response> Function(User?) req,
|
{required Future<Response> Function(User?) req,
|
||||||
Function(Map<String, dynamic>)? onOK,
|
Function(Map<String, dynamic>)? onOK,
|
||||||
bool Function()? onNetworkErr,
|
bool Function()? onNetworkErr,
|
||||||
bool Function()? onUnknownErr,
|
bool Function()? onUnknownErr,
|
||||||
bool Function()? onUserErr,
|
bool Function()? onUserErr,
|
||||||
|
Function()? onAnyErr,
|
||||||
Function()? after,
|
Function()? after,
|
||||||
bool Function(Map<String, dynamic>)? onServerErr,
|
bool Function(Map<String, dynamic>)? onServerErr,
|
||||||
bool needUser = true}) async {
|
bool needUser = true}) async {
|
||||||
|
@ -28,13 +29,16 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
|
|
||||||
if (showBar && sm != null) {
|
if (showBar && sm != null) {
|
||||||
showSimpleSnackbar(sm, text: 'No user found', action: 'Authorize',
|
showSimpleSnackbar(sm, text: 'No user found', action: 'Authorize',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
try {
|
try {
|
||||||
// try to remove broken user
|
// try to remove broken user
|
||||||
User.removeDisk();
|
User.removeDisk();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (onAnyErr != null) {
|
||||||
|
onAnyErr();
|
||||||
|
}
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
after();
|
after();
|
||||||
}
|
}
|
||||||
|
@ -56,7 +60,9 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
if (showBar && sm != null) {
|
if (showBar && sm != null) {
|
||||||
showSimpleSnackbar(sm, text: 'Network Error', action: 'Dismiss');
|
showSimpleSnackbar(sm, text: 'Network Error', action: 'Dismiss');
|
||||||
}
|
}
|
||||||
|
if (onAnyErr != null) {
|
||||||
|
onAnyErr();
|
||||||
|
}
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
after();
|
after();
|
||||||
}
|
}
|
||||||
|
@ -75,16 +81,16 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
if (onServerErr != null) {
|
if (onServerErr != null) {
|
||||||
showBar = onServerErr(res.body);
|
showBar = onServerErr(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showBar && sm != null) {
|
if (showBar && sm != null) {
|
||||||
showSimpleSnackbar(sm, text: errorAsString(res.body), action: 'OK');
|
showSimpleSnackbar(sm, text: errorAsString(res.body), action: 'OK');
|
||||||
}
|
}
|
||||||
|
if (onAnyErr != null) {
|
||||||
|
onAnyErr();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
bool showBar = true;
|
bool showBar = true;
|
||||||
|
|
||||||
print(e);
|
|
||||||
|
|
||||||
if (onUnknownErr != null) {
|
if (onUnknownErr != null) {
|
||||||
showBar = onUnknownErr();
|
showBar = onUnknownErr();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue