design: New chat access settings

This commit is contained in:
krille-chan 2024-04-14 15:49:46 +02:00
parent cbdea13772
commit c300220773
No known key found for this signature in database
11 changed files with 499 additions and 355 deletions

View file

@ -760,6 +760,9 @@
"type": "text",
"placeholders": {}
},
"globalChatId": "Global chat ID",
"accessAndVisibility": "Access and visibility",
"accessAndVisibilityDescription": "Who is allowed to join this chat and how the chat can be discovered.",
"calls": "Calls",
"customEmojisAndStickers": "Custom emojis and stickers",
"customEmojisAndStickersBody": "Add or share custom emojis or stickers which can be used in any chat.",
@ -2276,6 +2279,14 @@
"user": {}
}
},
"userWouldLikeToChangeTheChat": "{user} would like to join the chat.",
"@userWouldLikeToChangeTheChat": {
"placeholders": {
"user": {}
}
},
"noPublicLinkHasBeenCreatedYet": "No public link has been created yet",
"knock": "Knock",
"users": "Users",
"@users": {},
"unlockOldMessages": "Unlock old messages",
@ -2450,6 +2461,14 @@
"query": {}
}
},
"knocking": "Knocking",
"chatCanBeDiscoveredViaSearchOnServer": "Chat can be discovered via the search on {server}",
"@chatCanBeDiscoveredViaSearchOnServer": {
"type": "text",
"placeholders": {
"server": {}
}
},
"searchChatsRooms": "Search for #chats, @users...",
"@searchChatsRooms": {},
"nothingFound": "Nothing found...",

View file

@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/archive/archive.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
@ -344,6 +345,17 @@ abstract class AppRoutes {
),
),
routes: [
GoRoute(
path: 'access',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
ChatAccessSettings(
roomId: state.pathParameters['roomid']!,
),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'members',
pageBuilder: (context, state) => defaultPageBuilder(

View file

@ -0,0 +1,216 @@
import 'package:flutter/material.dart' hide Visibility;
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_page.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ChatAccessSettings extends StatefulWidget {
final String roomId;
const ChatAccessSettings({required this.roomId, super.key});
@override
State<ChatAccessSettings> createState() => ChatAccessSettingsController();
}
class ChatAccessSettingsController extends State<ChatAccessSettings> {
bool joinRulesLoading = false;
bool visibilityLoading = false;
bool historyVisibilityLoading = false;
bool guestAccessLoading = false;
Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!;
void setJoinRule(JoinRules? newJoinRules) async {
if (newJoinRules == null) return;
setState(() {
joinRulesLoading = true;
});
try {
await room.setJoinRules(newJoinRules);
} catch (e, s) {
Logs().w('Unable to change join rules', e, s);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toLocalizedString(context),
),
),
);
}
} finally {
if (mounted) {
setState(() {
joinRulesLoading = false;
});
}
}
}
void setHistoryVisibility(HistoryVisibility? historyVisibility) async {
if (historyVisibility == null) return;
setState(() {
historyVisibilityLoading = true;
});
try {
await room.setHistoryVisibility(historyVisibility);
} catch (e, s) {
Logs().w('Unable to change history visibility', e, s);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toLocalizedString(context),
),
),
);
}
} finally {
if (mounted) {
setState(() {
historyVisibilityLoading = false;
});
}
}
}
void setGuestAccess(GuestAccess? guestAccess) async {
if (guestAccess == null) return;
setState(() {
guestAccessLoading = true;
});
try {
await room.setGuestAccess(guestAccess);
} catch (e, s) {
Logs().w('Unable to change guest access', e, s);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toLocalizedString(context),
),
),
);
}
} finally {
if (mounted) {
setState(() {
guestAccessLoading = false;
});
}
}
}
void updateRoomAction() async {
final roomVersion = room
.getState(EventTypes.RoomCreate)!
.content
.tryGet<String>('room_version');
final capabilitiesResult = await showFutureLoadingDialog(
context: context,
future: () => room.client.getCapabilities(),
);
final capabilities = capabilitiesResult.result;
if (capabilities == null) return;
final newVersion = await showConfirmationDialog<String>(
context: context,
title: L10n.of(context)!.replaceRoomWithNewerVersion,
actions: capabilities.mRoomVersions!.available.entries
.where((r) => r.key != roomVersion)
.map(
(version) => AlertDialogAction(
key: version.key,
label:
'${version.key} (${version.value.toString().split('.').last})',
),
)
.toList(),
);
if (newVersion == null ||
OkCancelResult.cancel ==
await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.cancel,
title: L10n.of(context)!.areYouSure,
message: L10n.of(context)!.roomUpgradeDescription,
isDestructiveAction: true,
)) {
return;
}
await showFutureLoadingDialog(
context: context,
future: () => room.client.upgradeRoom(room.id, newVersion),
);
}
void setCanonicalAlias() async {
final input = await showTextInputDialog(
context: context,
title: L10n.of(context)!.editRoomAliases,
cancelLabel: L10n.of(context)!.cancel,
okLabel: L10n.of(context)!.ok,
textFields: [
DialogTextField(
prefixText: '#',
suffixText: room.client.userID!.domain!,
initialText: room.canonicalAlias.localpart,
),
],
);
final newAliasLocalpart = input?.singleOrNull?.trim();
if (newAliasLocalpart == null || newAliasLocalpart.isEmpty) return;
await showFutureLoadingDialog(
context: context,
future: () => room.setCanonicalAlias(
'#$newAliasLocalpart:${room.client.userID!.domain!}',
),
);
}
void setChatVisibilityOnDirectory(bool? visibility) async {
if (visibility == null) return;
setState(() {
visibilityLoading = true;
});
try {
await room.client.setRoomVisibilityOnDirectory(
room.id,
visibility: visibility == true ? Visibility.public : Visibility.private,
);
setState(() {});
} catch (e, s) {
Logs().w('Unable to change visibility', e, s);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toLocalizedString(context),
),
),
);
}
} finally {
if (mounted) {
setState(() {
visibilityLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return ChatAccessSettingsPageView(this);
}
}

View file

@ -0,0 +1,172 @@
import 'package:flutter/material.dart' hide Visibility;
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
class ChatAccessSettingsPageView extends StatelessWidget {
final ChatAccessSettingsController controller;
const ChatAccessSettingsPageView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
final room = controller.room;
return Scaffold(
appBar: AppBar(
leading: const Center(child: BackButton()),
title: Text(L10n.of(context)!.accessAndVisibility),
),
body: MaxWidthBody(
child: StreamBuilder<Object>(
stream: room.onUpdate.stream,
builder: (context, snapshot) => Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text(
L10n.of(context)!.visibilityOfTheChatHistory,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
for (final historyVisibility in HistoryVisibility.values)
RadioListTile<HistoryVisibility>.adaptive(
title: Text(
historyVisibility
.getLocalizedString(MatrixLocals(L10n.of(context)!)),
),
value: historyVisibility,
groupValue: room.historyVisibility,
onChanged: controller.historyVisibilityLoading ||
!room.canChangeHistoryVisibility
? null
: controller.setHistoryVisibility,
),
Divider(color: Theme.of(context).dividerColor),
ListTile(
title: Text(
L10n.of(context)!.whoIsAllowedToJoinThisGroup,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
for (final joinRule in JoinRules.values)
RadioListTile<JoinRules>.adaptive(
title: Text(
joinRule
.getLocalizedString(MatrixLocals(L10n.of(context)!)),
),
value: joinRule,
groupValue: room.joinRules,
onChanged:
controller.joinRulesLoading || !room.canChangeJoinRules
? null
: controller.setJoinRule,
),
Divider(color: Theme.of(context).dividerColor),
if ({JoinRules.public, JoinRules.knock}
.contains(room.joinRules)) ...[
ListTile(
title: Text(
L10n.of(context)!.areGuestsAllowedToJoin,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
for (final guestAccess in GuestAccess.values)
RadioListTile<GuestAccess>.adaptive(
title: Text(
guestAccess
.getLocalizedString(MatrixLocals(L10n.of(context)!)),
),
value: guestAccess,
groupValue: room.guestAccess,
onChanged: controller.guestAccessLoading ||
!room.canChangeGuestAccess
? null
: controller.setGuestAccess,
),
Divider(color: Theme.of(context).dividerColor),
FutureBuilder(
future: room.client.getRoomVisibilityOnDirectory(room.id),
builder: (context, snapshot) => SwitchListTile.adaptive(
value: snapshot.data == Visibility.public,
title: Text(
L10n.of(context)!.chatCanBeDiscoveredViaSearchOnServer(
room.client.userID!.domain!,
),
),
onChanged: controller.setChatVisibilityOnDirectory,
),
),
ListTile(
title: Text(L10n.of(context)!.publicLink),
subtitle: room.canonicalAlias.isEmpty
? Text(
L10n.of(context)!.noPublicLinkHasBeenCreatedYet,
style: const TextStyle(
fontStyle: FontStyle.italic,
),
)
: Text(
'https://matrix.to/#/${room.canonicalAlias}',
style: TextStyle(
decoration: TextDecoration.underline,
color: Theme.of(context).colorScheme.primary,
),
),
onTap: room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)
? controller.setCanonicalAlias
: null,
trailing: room.canonicalAlias.isEmpty
? const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Icon(Icons.add),
)
: IconButton(
icon: Icon(Icons.adaptive.share_outlined),
onPressed: () => FluffyShare.share(room.id, context),
),
),
],
ListTile(
title: Text(L10n.of(context)!.globalChatId),
subtitle: SelectableText(room.id),
trailing: IconButton(
icon: const Icon(Icons.copy_outlined),
onPressed: () => FluffyShare.share(room.id, context),
),
),
ListTile(
title: Text(L10n.of(context)!.roomVersion),
subtitle: SelectableText(
room
.getState(EventTypes.RoomCreate)!
.content
.tryGet<String>('room_version') ??
'Unknown',
),
trailing: room.canSendEvent(EventTypes.RoomTombstone)
? IconButton(
icon: const Icon(Icons.upgrade_outlined),
onPressed: controller.updateRoomAction,
)
: null,
),
],
),
),
),
);
}
}

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
@ -8,7 +7,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_details/chat_details_view.dart';
@ -71,116 +69,6 @@ class ChatDetailsController extends State<ChatDetails> {
}
}
void editAliases() async {
final room = Matrix.of(context).client.getRoomById(roomId!);
final aliasesResult = await showFutureLoadingDialog(
context: context,
future: () => room!.client.getLocalAliases(room.id),
);
final aliases = aliasesResult.result;
if (aliases == null) return;
final adminMode = room!.canSendEvent(EventTypes.RoomCanonicalAlias);
if (aliases.isEmpty && (room.canonicalAlias.isNotEmpty)) {
aliases.add(room.canonicalAlias);
}
if (aliases.isEmpty && adminMode) {
return setAliasAction();
}
final select = await showConfirmationDialog(
context: context,
title: L10n.of(context)!.editRoomAliases,
actions: [
if (adminMode)
AlertDialogAction(label: L10n.of(context)!.create, key: 'new'),
...aliases.map((alias) => AlertDialogAction(key: alias, label: alias)),
],
);
if (select == null) return;
if (select == 'new') {
return setAliasAction();
}
final option = await showConfirmationDialog<AliasActions>(
context: context,
title: select,
actions: [
AlertDialogAction(
label: L10n.of(context)!.copyToClipboard,
key: AliasActions.copy,
isDefaultAction: true,
),
if (adminMode) ...{
AlertDialogAction(
label: L10n.of(context)!.setAsCanonicalAlias,
key: AliasActions.setCanonical,
isDestructiveAction: true,
),
AlertDialogAction(
label: L10n.of(context)!.delete,
key: AliasActions.delete,
isDestructiveAction: true,
),
},
],
);
if (option == null) return;
switch (option) {
case AliasActions.copy:
await Clipboard.setData(ClipboardData(text: select));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)),
);
break;
case AliasActions.delete:
await showFutureLoadingDialog(
context: context,
future: () => room.client.deleteRoomAlias(select),
);
break;
case AliasActions.setCanonical:
await showFutureLoadingDialog(
context: context,
future: () => room.client.setRoomStateWithKey(
room.id,
EventTypes.RoomCanonicalAlias,
'',
{
'alias': select,
},
),
);
break;
}
}
void setAliasAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final domain = room.client.userID!.domain;
final input = await showTextInputDialog(
context: context,
title: L10n.of(context)!.setInvitationLink,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
prefixText: '#',
suffixText: domain,
hintText: L10n.of(context)!.alias,
initialText: room.canonicalAlias.localpart,
),
],
);
if (input == null) return;
await showFutureLoadingDialog(
context: context,
future: () =>
room.client.setRoomAlias('#${input.single}:${domain!}', room.id),
);
}
void setTopicAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog(
@ -211,91 +99,6 @@ class ChatDetailsController extends State<ChatDetails> {
}
}
void setGuestAccess() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final currentGuestAccess = room.guestAccess;
final newGuestAccess = await showConfirmationDialog<GuestAccess>(
context: context,
title: L10n.of(context)!.areGuestsAllowedToJoin,
actions: GuestAccess.values
.map(
(guestAccess) => AlertDialogAction(
key: guestAccess,
label: guestAccess
.getLocalizedString(MatrixLocals(L10n.of(context)!)),
isDefaultAction: guestAccess == currentGuestAccess,
),
)
.toList(),
);
if (newGuestAccess == null || newGuestAccess == currentGuestAccess) return;
await showFutureLoadingDialog(
context: context,
future: () => room.setGuestAccess(newGuestAccess),
);
}
void setHistoryVisibility() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final currentHistoryVisibility = room.historyVisibility;
final newHistoryVisibility =
await showConfirmationDialog<HistoryVisibility>(
context: context,
title: L10n.of(context)!.visibilityOfTheChatHistory,
actions: HistoryVisibility.values
.map(
(visibility) => AlertDialogAction(
key: visibility,
label: visibility
.getLocalizedString(MatrixLocals(L10n.of(context)!)),
isDefaultAction: visibility == currentHistoryVisibility,
),
)
.toList(),
);
if (newHistoryVisibility == null ||
newHistoryVisibility == currentHistoryVisibility) return;
await showFutureLoadingDialog(
context: context,
future: () => room.setHistoryVisibility(newHistoryVisibility),
);
}
void setJoinRules() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final currentJoinRule = room.joinRules;
final newJoinRule = await showConfirmationDialog<JoinRules>(
context: context,
title: L10n.of(context)!.whoIsAllowedToJoinThisGroup,
actions: JoinRules.values
.map(
(joinRule) => AlertDialogAction(
key: joinRule,
label:
joinRule.getLocalizedString(MatrixLocals(L10n.of(context)!)),
isDefaultAction: joinRule == currentJoinRule,
),
)
.toList(),
);
if (newJoinRule == null || newJoinRule == currentJoinRule) return;
await showFutureLoadingDialog(
context: context,
future: () async {
await room.setJoinRules(newJoinRule);
room.client.setRoomVisibilityOnDirectory(
roomId!,
visibility: {
JoinRules.public,
JoinRules.knock,
}.contains(newJoinRule)
? matrix.Visibility.public
: matrix.Visibility.private,
);
},
);
}
void goToEmoteSettings() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
// okay, we need to test if there are any emote state events other than the default one

View file

@ -267,23 +267,6 @@ class ChatDetailsView extends StatelessWidget {
height: 1,
color: Theme.of(context).dividerColor,
),
if (room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.link_outlined),
),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.editAliases,
title: Text(L10n.of(context)!.editRoomAliases),
subtitle: Text(
(room.canonicalAlias.isNotEmpty)
? room.canonicalAlias
: L10n.of(context)!.none,
),
),
ListTile(
leading: CircleAvatar(
backgroundColor:
@ -308,69 +291,14 @@ class ChatDetailsView extends StatelessWidget {
child: const Icon(Icons.shield_outlined),
),
title: Text(
L10n.of(context)!.whoIsAllowedToJoinThisGroup,
),
trailing: room.canChangeJoinRules
? const Icon(Icons.chevron_right_outlined)
: null,
subtitle: Text(
room.joinRules?.getLocalizedString(
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
),
onTap: room.canChangeJoinRules
? controller.setJoinRules
: null,
),
if (!room.isDirectChat)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.visibility_outlined),
),
trailing: room.canChangeHistoryVisibility
? const Icon(Icons.chevron_right_outlined)
: null,
title: Text(
L10n.of(context)!.visibilityOfTheChatHistory,
L10n.of(context)!.accessAndVisibility,
),
subtitle: Text(
room.historyVisibility?.getLocalizedString(
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
L10n.of(context)!.accessAndVisibilityDescription,
),
onTap: room.canChangeHistoryVisibility
? controller.setHistoryVisibility
: null,
),
if (room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.person_add_alt_1_outlined,
),
),
trailing: room.canChangeGuestAccess
? const Icon(Icons.chevron_right_outlined)
: null,
title: Text(
L10n.of(context)!.areGuestsAllowedToJoin,
),
subtitle: Text(
room.guestAccess.getLocalizedString(
MatrixLocals(L10n.of(context)!),
),
),
onTap: room.canChangeGuestAccess
? controller.setGuestAccess
: null,
onTap: () => context
.push('/rooms/${room.id}/details/access'),
trailing: const Icon(Icons.chevron_right_outlined),
),
if (!room.isDirectChat)
ListTile(

View file

@ -14,12 +14,14 @@ class ParticipantListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final membershipBatch = <Membership, String>{
Membership.join: '',
Membership.ban: L10n.of(context)!.banned,
Membership.invite: L10n.of(context)!.invited,
Membership.leave: L10n.of(context)!.leftTheChat,
final membershipBatch = switch (user.membership) {
Membership.ban => L10n.of(context)!.banned,
Membership.invite => L10n.of(context)!.invited,
Membership.join => null,
Membership.knock => L10n.of(context)!.knocking,
Membership.leave => L10n.of(context)!.leftTheChat,
};
final permissionBatch = user.powerLevel == 100
? L10n.of(context)!.admin
: user.powerLevel >= 50
@ -66,7 +68,7 @@ class ParticipantListItem extends StatelessWidget {
),
),
),
membershipBatch[user.membership]!.isEmpty
membershipBatch == null
? const SizedBox.shrink()
: Container(
padding: const EdgeInsets.all(4),
@ -75,8 +77,7 @@ class ParticipantListItem extends StatelessWidget {
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(8),
),
child:
Center(child: Text(membershipBatch[user.membership]!)),
child: Center(child: Text(membershipBatch)),
),
],
),

View file

@ -2,7 +2,6 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
@ -71,44 +70,6 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
false),
);
void updateRoomAction(Capabilities capabilities) async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final roomVersion = room
.getState(EventTypes.RoomCreate)!
.content['room_version'] as String? ??
'1';
final newVersion = await showConfirmationDialog<String>(
context: context,
title: L10n.of(context)!.replaceRoomWithNewerVersion,
actions: capabilities.mRoomVersions!.available.entries
.where((r) => r.key != roomVersion)
.map(
(version) => AlertDialogAction(
key: version.key,
label:
'${version.key} (${version.value.toString().split('.').last})',
),
)
.toList(),
);
if (newVersion == null ||
OkCancelResult.cancel ==
await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.cancel,
title: L10n.of(context)!.areYouSure,
message: L10n.of(context)!.roomUpgradeDescription,
)) {
return;
}
await showFutureLoadingDialog(
context: context,
future: () => room.client.upgradeRoom(roomId!, newVersion),
).then((_) => context.pop());
}
@override
Widget build(BuildContext context) => ChatPermissionsSettingsView(this);
}

View file

@ -54,7 +54,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
entry.value,
),
),
const Divider(thickness: 1),
Divider(color: Theme.of(context).dividerColor),
ListTile(
title: Text(
L10n.of(context)!.notifications,
@ -87,7 +87,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
);
},
),
const Divider(thickness: 1),
Divider(color: Theme.of(context).dividerColor),
ListTile(
title: Text(
L10n.of(context)!.configureChat,
@ -109,33 +109,6 @@ class ChatPermissionsSettingsView extends StatelessWidget {
category: 'events',
),
),
if (room.canSendEvent(EventTypes.RoomTombstone)) ...{
const Divider(thickness: 1),
FutureBuilder<Capabilities>(
future: room.client.getCapabilities(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
);
}
final roomVersion = room
.getState(EventTypes.RoomCreate)!
.content['room_version'] as String? ??
'1';
return ListTile(
title: Text(
'${L10n.of(context)!.roomVersion}: $roomVersion',
),
onTap: () =>
controller.updateRoomAction(snapshot.data!),
);
},
),
},
],
),
],

View file

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/url_launcher.dart';
@ -105,6 +107,57 @@ class UserBottomSheetView extends StatelessWidget {
),
body: ListView(
children: [
if (user?.membership == Membership.knock)
Padding(
padding: const EdgeInsets.all(12.0),
child: Material(
color: Theme.of(context).colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: ListTile(
minVerticalPadding: 16,
title: Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Text(
L10n.of(context)!
.userWouldLikeToChangeTheChat(displayname),
),
),
subtitle: Row(
children: [
TextButton.icon(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.background,
foregroundColor:
Theme.of(context).colorScheme.primary,
),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () => user!.room.invite(user.id),
),
icon: const Icon(Icons.check_outlined),
label: Text(L10n.of(context)!.accept),
),
const SizedBox(width: 12),
TextButton.icon(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.errorContainer,
foregroundColor:
Theme.of(context).colorScheme.onErrorContainer,
),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () => user!.room.kick(user.id),
),
icon: const Icon(Icons.cancel_outlined),
label: Text(L10n.of(context)!.decline),
),
],
),
),
),
),
Row(
children: [
Padding(

View file

@ -31,22 +31,26 @@ class PublicRoomBottomSheet extends StatelessWidget {
void _joinRoom(BuildContext context) async {
final client = Matrix.of(outerContext).client;
final chunk = this.chunk;
final knock = chunk?.joinRule == 'knock';
final result = await showFutureLoadingDialog<String>(
context: context,
future: () async {
if (chunk != null && client.getRoomById(chunk.roomId) != null) {
return chunk.roomId;
}
final roomId = chunk != null && chunk.joinRule == 'knock'
final roomId = chunk != null && knock
? await client.knockRoom(chunk.roomId)
: await client.joinRoom(roomAlias ?? chunk!.roomId);
if (client.getRoomById(roomId) == null) {
if (!knock && client.getRoomById(roomId) == null) {
await client.waitForRoomInSync(roomId);
}
return roomId;
},
);
if (knock) {
return;
}
if (result.error == null) {
Navigator.of(context).pop();
// don't open the room if the joined room is a space
@ -138,9 +142,11 @@ class PublicRoomBottomSheet extends StatelessWidget {
child: ElevatedButton.icon(
onPressed: () => _joinRoom(context),
label: Text(
chunk?.roomType == 'm.space'
? L10n.of(context)!.joinSpace
: L10n.of(context)!.joinRoom,
chunk?.joinRule == 'knock'
? L10n.of(context)!.knock
: chunk?.roomType == 'm.space'
? L10n.of(context)!.joinSpace
: L10n.of(context)!.joinRoom,
),
icon: const Icon(Icons.login_outlined),
),