diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 40ac982cd..67d151916 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4857,6 +4857,7 @@ "keepPracticing": "Keep practicing!", "niceJob": "Nice job!", "publicSpacesTitle": "Learning communities", + "askToJoin": "Ask to join", "emptyChatWarningTitle": "Chat is empty", "emptyChatWarningDesc": "You haven't invited anyone to your chat. Go to Chat settings to invite your contacts or the Bot. You can also do this later." } \ No newline at end of file diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index f8b72faa7..e08d0af85 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -13,11 +13,11 @@ import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/pangea/chat_list/widgets/pangea_chat_list_header.dart'; +import 'package:fluffychat/pangea/public_spaces/pangea_public_room_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import '../../config/themes.dart'; import '../../widgets/matrix.dart'; @@ -419,7 +419,10 @@ class PublicRoomsHorizontalListState extends State { avatar: publicRooms[i].avatarUrl, onPressed: () => showAdaptiveBottomSheet( context: context, - builder: (c) => PublicRoomBottomSheet( + // #Pangea + // builder: (c) => PublicRoomBottomSheet( + builder: (c) => PangeaPublicRoomBottomSheet( + // Pangea# roomAlias: publicRooms[i].canonicalAlias ?? publicRooms[i].roomId, outerContext: context, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 6f0f706ae..7cbf9b34d 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -16,6 +16,7 @@ import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/public_spaces/pangea_public_room_bottom_sheet.dart'; import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; @@ -25,7 +26,6 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart' import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; enum AddRoomType { chat, @@ -266,7 +266,10 @@ class _SpaceViewState extends State { final joined = await showAdaptiveBottomSheet( context: context, - builder: (_) => PublicRoomBottomSheet( + // #Pangea + // builder: (_) => PublicRoomBottomSheet( + builder: (_) => PangeaPublicRoomBottomSheet( + // Pangea# outerContext: context, chunk: item, via: space?.spaceChildren diff --git a/lib/pangea/public_spaces/pangea_public_room_bottom_sheet.dart b/lib/pangea/public_spaces/pangea_public_room_bottom_sheet.dart new file mode 100644 index 000000000..11374cc0b --- /dev/null +++ b/lib/pangea/public_spaces/pangea_public_room_bottom_sheet.dart @@ -0,0 +1,360 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/mxc_image.dart'; + +class PangeaPublicRoomBottomSheet extends StatefulWidget { + final String? roomAlias; + final BuildContext outerContext; + final PublicRoomsChunk? chunk; + final List? via; + + PangeaPublicRoomBottomSheet({ + this.roomAlias, + required this.outerContext, + this.chunk, + this.via, + super.key, + }) { + assert(roomAlias != null || chunk != null); + } + + @override + State createState() => PangeaPublicRoomBottomSheetState(); +} + +class PangeaPublicRoomBottomSheetState + extends State { + BuildContext get outerContext => widget.outerContext; + String? get roomAlias => widget.roomAlias; + PublicRoomsChunk? get chunk => widget.chunk; + List? get via => widget.via; + + final TextEditingController _codeController = TextEditingController(); + late Client client; + + @override + void initState() { + super.initState(); + client = Matrix.of(outerContext).client; + } + + @override + void dispose() { + _codeController.dispose(); + super.dispose(); + } + + Room? get room => client.getRoomById(chunk!.roomId); + bool get _isRoomMember => room != null && room!.membership == Membership.join; + bool get _isKnockRoom => widget.chunk?.joinRule == 'knock'; + + Future _joinWithCode() async { + await MatrixState.pangeaController.classController.joinClasswithCode( + context, + _codeController.text, + notFoundError: L10n.of(context).notTheCodeError, + ); + Navigator.of(context).pop(); + } + + void _goToRoom(String roomID) { + if (chunk?.roomType != 'm.space' && !client.getRoomById(roomID)!.isSpace) { + outerContext.go("/rooms/$roomID"); + } else { + outerContext.push('/rooms/$roomID/details'); + } + } + + Future _joinRoom() async { + if (_isRoomMember) { + _goToRoom(room!.id); + Navigator.of(context).pop(); + return; + } + + final result = await showFutureLoadingDialog( + context: context, + future: () async { + if (chunk != null && client.getRoomById(chunk!.roomId) != null) { + return chunk!.roomId; + } + final roomId = await client.joinRoom( + roomAlias ?? chunk!.roomId, + serverName: via, + ); + + if (client.getRoomById(roomId) == null) { + await client.waitForRoomInSync(roomId); + } + return roomId; + }, + ); + + if (result.result != null) { + _goToRoom(result.result!); + Navigator.of(context).pop(); + } + } + + Future _knockRoom() async { + if (_isRoomMember) { + _goToRoom(room!.id); + Navigator.of(context).pop(); + return; + } + + await showFutureLoadingDialog( + context: context, + future: () async => client.knockRoom( + roomAlias ?? chunk!.roomId, + serverName: via, + ), + onSuccess: () => L10n.of(context).knockSpaceSuccess, + delay: false, + ); + } + + bool testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias; + + Future search() async { + final chunk = this.chunk; + if (chunk != null) return chunk; + final query = await Matrix.of(outerContext).client.queryPublicRooms( + server: roomAlias!.domain, + filter: PublicRoomQueryFilter( + genericSearchTerm: roomAlias, + ), + ); + if (!query.chunk.any(testRoom)) { + throw (L10n.of(outerContext).noRoomsFound); + } + return query.chunk.firstWhere(testRoom); + } + + @override + Widget build(BuildContext context) { + final roomAlias = this.roomAlias ?? chunk?.canonicalAlias; + return SafeArea( + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text( + chunk?.name ?? roomAlias ?? chunk?.roomId ?? 'Unknown', + overflow: TextOverflow.fade, + ), + actions: [ + Center( + child: CloseButton( + onPressed: Navigator.of(context, rootNavigator: false).pop, + ), + ), + ], + ), + body: FutureBuilder( + future: search(), + builder: (context, snapshot) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + spacing: 32.0, + children: [ + Row( + spacing: 16.0, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: MxcImage( + uri: chunk?.avatarUrl, + width: 160.0, + height: 160.0, + fit: BoxFit.cover, + ), + ), + Expanded( + child: SizedBox( + height: 160.0, + child: Column( + spacing: 16.0, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const Icon(Icons.group), + Text( + L10n.of(context).countParticipants( + chunk?.numJoinedMembers ?? 1, + ), + ), + ], + ), + if (chunk?.topic != null) + Flexible( + child: SingleChildScrollView( + child: Text( + chunk!.topic!, + softWrap: true, + maxLines: null, + ), + ), + ), + ], + ), + ), + ), + ], + ), + Column( + spacing: 8.0, + children: _isKnockRoom + ? [ + Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: BorderRadius.circular(24), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _codeController, + decoration: const InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + ), + ), + ), + Container( + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: Theme.of(context) + .colorScheme + .outline, + ), + ), + ), + child: ElevatedButton( + onPressed: _joinWithCode, + style: ElevatedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.zero, + bottomLeft: Radius.zero, + topRight: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + ), + ), + child: Text(L10n.of(context).join), + ), + ), + ], + ), + ), + ElevatedButton( + onPressed: _knockRoom, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Symbols.door_open, + size: 20.0, + ), + Text(L10n.of(context).askToJoin), + ], + ), + ), + if (roomAlias != null) + ElevatedButton( + onPressed: () { + FluffyShare.share( + "${Environment.frontendURL}/#/join_with_alias?alias=${Uri.encodeComponent(roomAlias)}", + context, + ); + Navigator.of(context).pop(); + }, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.copy_outlined, + size: 20.0, + ), + Flexible( + child: Text( + roomAlias, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ] + : [ + ElevatedButton( + onPressed: _joinRoom, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.join_full_outlined, + size: 20.0, + ), + Text(L10n.of(context).join), + ], + ), + ), + if (roomAlias != null) + ElevatedButton( + onPressed: () { + FluffyShare.share( + "${Environment.frontendURL}/#/join_with_alias?alias=${Uri.encodeComponent(roomAlias)}", + context, + ); + Navigator.of(context).pop(); + }, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.copy_outlined, + size: 20.0, + ), + Flexible( + child: Text( + roomAlias, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pangea/public_spaces/public_space_card.dart b/lib/pangea/public_spaces/public_space_card.dart index b01a33c33..5d241209a 100644 --- a/lib/pangea/public_spaces/public_space_card.dart +++ b/lib/pangea/public_spaces/public_space_card.dart @@ -4,9 +4,9 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; +import 'package:fluffychat/pangea/public_spaces/pangea_public_room_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; class PublicSpaceCard extends StatelessWidget { final PublicRoomsChunk space; @@ -27,7 +27,10 @@ class PublicSpaceCard extends StatelessWidget { return PressableButton( onPressed: () => showAdaptiveBottomSheet( context: context, - builder: (c) => PublicRoomBottomSheet( + // #Pangea + // builder: (c) => PublicRoomBottomSheet( + builder: (c) => PangeaPublicRoomBottomSheet( + // Pangea# roomAlias: space.canonicalAlias ?? space.roomId, outerContext: context, chunk: space, diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index 82bfedefa..84516bc41 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -9,11 +9,11 @@ import 'package:url_launcher/url_launcher_string.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; +import 'package:fluffychat/pangea/public_spaces/pangea_public_room_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import 'platform_infos.dart'; class UrlLauncher { @@ -184,7 +184,10 @@ class UrlLauncher { } else { await showAdaptiveBottomSheet( context: context, - builder: (c) => PublicRoomBottomSheet( + // #Pangea + // builder: (c) => PublicRoomBottomSheet( + builder: (c) => PangeaPublicRoomBottomSheet( + // Pangea# roomAlias: identityParts.primaryIdentifier, outerContext: context, ),