diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 32ce9160e..37470efcb 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -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...", diff --git a/lib/config/routes.dart b/lib/config/routes.dart index a685bb789..e7efe9d29 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -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( diff --git a/lib/pages/chat_access_settings/chat_access_settings_controller.dart b/lib/pages/chat_access_settings/chat_access_settings_controller.dart new file mode 100644 index 000000000..068a47f37 --- /dev/null +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -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 createState() => ChatAccessSettingsController(); +} + +class ChatAccessSettingsController extends State { + 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('room_version'); + final capabilitiesResult = await showFutureLoadingDialog( + context: context, + future: () => room.client.getCapabilities(), + ); + final capabilities = capabilitiesResult.result; + if (capabilities == null) return; + final newVersion = await showConfirmationDialog( + 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); + } +} diff --git a/lib/pages/chat_access_settings/chat_access_settings_page.dart b/lib/pages/chat_access_settings/chat_access_settings_page.dart new file mode 100644 index 000000000..8c5004733 --- /dev/null +++ b/lib/pages/chat_access_settings/chat_access_settings_page.dart @@ -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( + 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.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.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.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('room_version') ?? + 'Unknown', + ), + trailing: room.canSendEvent(EventTypes.RoomTombstone) + ? IconButton( + icon: const Icon(Icons.upgrade_outlined), + onPressed: controller.updateRoomAction, + ) + : null, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 75614b45f..6c78adda6 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -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 { } } - 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( - 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 { } } - void setGuestAccess() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final currentGuestAccess = room.guestAccess; - final newGuestAccess = await showConfirmationDialog( - 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( - 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( - 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 diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 127a98507..387f6e8cd 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -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( diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index e044e38e7..661153634 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -14,12 +14,14 @@ class ParticipantListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final membershipBatch = { - 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)), ), ], ), diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index a6cdcbef0..4d3bdaa09 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -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 { 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( - 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); } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index b88095b20..11aad87ea 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -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( - 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!), - ); - }, - ), - }, ], ), ], diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 93774a8ca..c7eb6a29b 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -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( diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 19932f5b9..7871bc903 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -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( 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), ),