diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 08a23cdb9..889aa2011 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3205,5 +3205,14 @@ "takeAPhoto": "Take a photo", "recordAVideo": "Record a video", "optionalMessage": "(Optional) message...", - "notSupportedOnThisDevice": "Not supported on this device" + "notSupportedOnThisDevice": "Not supported on this device", + "noRoomFoundForAlias": "No room found with the alias {alias} on your server.", + "@noRoomFoundForAlias": { + "type": "String", + "placeholders": { + "alias": { + "type": "String" + } + } + } } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index c34fd8201..277ba25f3 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -35,7 +35,9 @@ import 'package:fluffychat/widgets/layouts/empty_page.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; import 'package:fluffychat/widgets/log_view.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/room_loader.dart'; import 'package:fluffychat/widgets/share_scaffold_dialog.dart'; +import 'package:matrix/matrix_api_lite.dart'; abstract class AppRoutes { static FutureOr loggedInRedirect( @@ -132,9 +134,15 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - ChatPage( + RoomLoader( roomId: state.pathParameters['roomid']!, - eventId: state.uri.queryParameters['event'], + chunk: state.extra is PublicRoomsChunk + ? state.extra as PublicRoomsChunk + : null, + builder: (context, room) => ChatPage( + room: room, + eventId: state.uri.queryParameters['event'], + ), ), ), redirect: loggedOutRedirect, @@ -331,10 +339,16 @@ abstract class AppRoutes { return defaultPageBuilder( context, state, - ChatPage( + RoomLoader( + key: ValueKey(state.pathParameters['roomid']!), roomId: state.pathParameters['roomid']!, - shareItems: shareItems, - eventId: state.uri.queryParameters['event'], + chunk: state.extra is PublicRoomsChunk + ? state.extra as PublicRoomsChunk + : null, + builder: (context, room) => ChatPage( + room: room, + eventId: state.uri.queryParameters['event'], + ), ), ); }, diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 23058cd26..fac2ff998 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -44,48 +44,12 @@ import '../../utils/localized_exception_extension.dart'; import 'send_file_dialog.dart'; import 'send_location_dialog.dart'; -class ChatPage extends StatelessWidget { - final String roomId; - final List? shareItems; - final String? eventId; - - const ChatPage({ - super.key, - required this.roomId, - this.eventId, - this.shareItems, - }); - - @override - Widget build(BuildContext context) { - final room = Matrix.of(context).client.getRoomById(roomId); - if (room == null) { - return Scaffold( - appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)), - body: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat), - ), - ), - ); - } - - return ChatPageWithRoom( - key: Key('chat_page_${roomId}_$eventId'), - room: room, - shareItems: shareItems, - eventId: eventId, - ); - } -} - -class ChatPageWithRoom extends StatefulWidget { +class ChatPage extends StatefulWidget { final Room room; final List? shareItems; final String? eventId; - const ChatPageWithRoom({ + const ChatPage({ super.key, required this.room, this.shareItems, @@ -96,8 +60,7 @@ class ChatPageWithRoom extends StatefulWidget { ChatController createState() => ChatController(); } -class ChatController extends State - with WidgetsBindingObserver { +class ChatController extends State with WidgetsBindingObserver { Room get room => sendingClient.getRoomById(roomId) ?? widget.room; late Client sendingClient; diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 7a54a49d4..3e8f1a514 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -100,7 +100,10 @@ class ChatEventList extends StatelessWidget { child: CircularProgressIndicator.adaptive(strokeWidth: 2), ); } - if (timeline.canRequestHistory) { + if (timeline.canRequestHistory && + (timeline.room.membership == Membership.join || + timeline.room.historyVisibility == + HistoryVisibility.worldReadable)) { return Builder( builder: (context) { WidgetsBinding.instance diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 6c63441e8..f9bdcd8b7 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -18,9 +18,7 @@ import 'package:fluffychat/pages/chat/pinned_events.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/utils/account_config.dart'; -import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:fluffychat/widgets/unread_rooms_badge.dart'; @@ -133,13 +131,7 @@ class ChatView extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - if (controller.room.membership == Membership.invite) { - showFutureLoadingDialog( - context: context, - future: () => controller.room.join(), - exceptionContext: ExceptionContext.joinRoom, - ); - } + final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0; final scrollUpBannerEventId = controller.scrollUpBannerEventId; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 48e4471f9..950a7f778 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -112,31 +112,6 @@ class ChatListController extends State }); void onChatTap(Room room) async { - if (room.membership == Membership.invite) { - final joinResult = await showFutureLoadingDialog( - context: context, - future: () async { - final waitForRoom = room.client.waitForRoomInSync( - room.id, - join: true, - ); - await room.join(); - await waitForRoom; - }, - exceptionContext: ExceptionContext.joinRoom, - ); - if (joinResult.error != null) return; - } - - if (room.membership == Membership.ban) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context).youHaveBeenBannedFromThisChat), - ), - ); - return; - } - if (room.membership == Membership.leave) { context.go('/rooms/archive/${room.id}'); return; diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index ffe989c89..431e816a2 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -16,7 +17,6 @@ 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 '../../config/themes.dart'; import '../../widgets/matrix.dart'; import 'chat_list_header.dart'; @@ -344,14 +344,9 @@ class PublicRoomsHorizontalList extends StatelessWidget { publicRooms[i].canonicalAlias?.localpart ?? L10n.of(context).group, avatar: publicRooms[i].avatarUrl, - onPressed: () => showAdaptiveBottomSheet( - context: context, - builder: (c) => PublicRoomBottomSheet( - roomAlias: - publicRooms[i].canonicalAlias ?? publicRooms[i].roomId, - outerContext: context, - chunk: publicRooms[i], - ), + onPressed: () => context.go( + '/rooms/${publicRooms[i].roomId}', + extra: publicRooms[i], ), ), ), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index b39d96a6d..385c7d587 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart' as sdk; @@ -10,7 +9,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; -import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart'; @@ -19,7 +17,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, subspace } @@ -96,29 +93,6 @@ class _SpaceViewState extends State { } } - void _joinChildRoom(SpaceRoomsChunk item) async { - final client = Matrix.of(context).client; - final space = client.getRoomById(widget.spaceId); - - final joined = await showAdaptiveBottomSheet( - context: context, - builder: (_) => PublicRoomBottomSheet( - outerContext: context, - chunk: item, - via: space?.spaceChildren - .firstWhereOrNull( - (child) => child.roomId == item.roomId, - ) - ?.via, - ), - ); - if (mounted && joined == true) { - setState(() { - _discoveredChildren.remove(item); - }); - } - } - void _onSpaceAction(SpaceActions action) async { final space = Matrix.of(context).client.getRoomById(widget.spaceId); @@ -499,7 +473,7 @@ class _SpaceViewState extends State { const VisualDensity(vertical: -0.5), contentPadding: const EdgeInsets.symmetric(horizontal: 8), - onTap: () => _joinChildRoom(item), + onTap: () => context.go('/rooms/${item.roomId}'), leading: Avatar( mxContent: item.avatarUrl, name: displayname, diff --git a/lib/utils/room_from_public_rooms_chunk.dart b/lib/utils/room_from_public_rooms_chunk.dart new file mode 100644 index 000000000..aa7ff41cb --- /dev/null +++ b/lib/utils/room_from_public_rooms_chunk.dart @@ -0,0 +1,84 @@ +import 'package:matrix/matrix.dart'; + +extension RoomFromPublicRoomsChunk on PublicRoomsChunk { + Room createRoom(Client client) { + final room = Room( + id: roomId, + client: client, + prev_batch: '', + membership: Membership.leave, + ); + if (guestCanJoin) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.GuestAccess, + content: {'guest_access': 'can_join'}, + senderId: '', + ), + ); + } + if (worldReadable) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.HistoryVisibility, + content: {'history_visibility': 'world_readable'}, + senderId: '', + ), + ); + } + if (avatarUrl != null) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.RoomAvatar, + content: {'url': avatarUrl.toString()}, + senderId: '', + ), + ); + } + if (canonicalAlias != null) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.RoomCanonicalAlias, + content: {'alias': canonicalAlias}, + senderId: '', + ), + ); + } + if (joinRule != null) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.RoomJoinRules, + content: {'join_rule': joinRule}, + senderId: '', + ), + ); + } + room.summary.mInvitedMemberCount = numJoinedMembers; + + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.RoomCreate, + content: {if (roomType != null) 'type': roomType}, + senderId: '', + ), + ); + + if (name != null) { + room.setState( + StrippedStateEvent( + stateKey: '', + type: EventTypes.RoomName, + content: {'name': name}, + senderId: '', + ), + ); + } + return room; + } +} diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index 80efed80e..9af81dc7a 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -13,7 +13,6 @@ 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 { @@ -134,92 +133,49 @@ class UrlLauncher { if (identityParts == null) { return; // no match, nothing to do } - if (identityParts.primaryIdentifier.sigil == '#' || - identityParts.primaryIdentifier.sigil == '!') { + + if (identityParts.primaryIdentifier.sigil == '!') { + final event = identityParts.secondaryIdentifier; + context.go( + '/${Uri( + pathSegments: ['rooms', identityParts.primaryIdentifier], + queryParameters: event != null ? {'event': event} : null, + )}', + ); + } + + if (identityParts.primaryIdentifier.sigil == '#') { // we got a room! Let's open that one final roomIdOrAlias = identityParts.primaryIdentifier; final event = identityParts.secondaryIdentifier; - var room = matrix.client.getRoomByAlias(roomIdOrAlias) ?? - matrix.client.getRoomById(roomIdOrAlias); - var roomId = room?.id; - // we make the servers a set and later on convert to a list, so that we can easily - // deduplicate servers added via alias lookup and query parameter - final servers = {}; - if (room == null && roomIdOrAlias.sigil == '#') { + var roomId = matrix.client.getRoomByAlias(roomIdOrAlias)?.id; + + if (roomId == null && roomIdOrAlias.sigil == '#') { // we were unable to find the room locally...so resolve it final response = await showFutureLoadingDialog( context: context, future: () => matrix.client.getRoomIdByAlias(roomIdOrAlias), ); - if (response.error != null) { - return; // nothing to do, the alias doesn't exist - } - roomId = response.result!.roomId; - servers.addAll(response.result!.servers!); - room = matrix.client.getRoomById(roomId!); + roomId = response.result?.roomId; } - servers.addAll(identityParts.via); - if (room != null) { - if (room.isSpace) { - // TODO: Implement navigate to space - context.go('/rooms/${room.id}'); - return; - } - // we have the room, so....just open it - if (event != null) { - context.go( - '/${Uri( - pathSegments: ['rooms', room.id], - queryParameters: {'event': event}, - )}', - ); - } else { - context.go('/rooms/${room.id}'); - } - return; - } else { - await showAdaptiveBottomSheet( + if (roomId == null) { + await showOkAlertDialog( context: context, - builder: (c) => PublicRoomBottomSheet( - roomAlias: identityParts.primaryIdentifier, - outerContext: context, - ), + message: L10n.of(context) + .noRoomFoundForAlias(identityParts.primaryIdentifier), + title: L10n.of(context).nothingFound, ); + return; } - if (roomIdOrAlias.sigil == '!') { - if (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: 'Join room $roomIdOrAlias', - ) == - OkCancelResult.ok) { - roomId = roomIdOrAlias; - final response = await showFutureLoadingDialog( - context: context, - future: () => matrix.client.joinRoom( - roomIdOrAlias, - serverName: servers.isNotEmpty ? servers.toList() : null, - ), - ); - if (response.error != null) return; - // wait for two seconds so that it probably came down /sync - await showFutureLoadingDialog( - context: context, - future: () => Future.delayed(const Duration(seconds: 2)), - ); - if (event != null) { - context.go( - Uri( - pathSegments: ['rooms', response.result!], - queryParameters: {'event': event}, - ).toString(), - ); - } else { - context.go('/rooms/${response.result!}'); - } - } - } + context.go( + '/${Uri( + pathSegments: ['rooms', roomId], + queryParameters: event != null ? {'event': event} : null, + )}', + ); + + return; } else if (identityParts.primaryIdentifier.sigil == '@') { await showAdaptiveBottomSheet( context: context, diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart deleted file mode 100644 index 0187e41e5..000000000 --- a/lib/widgets/public_room_bottom_sheet.dart +++ /dev/null @@ -1,233 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_linkify/flutter_linkify.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/utils/fluffy_share.dart'; -import 'package:fluffychat/utils/url_launcher.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/qr_code_viewer.dart'; - -class PublicRoomBottomSheet extends StatelessWidget { - final String? roomAlias; - final BuildContext outerContext; - final PublicRoomsChunk? chunk; - final List? via; - - PublicRoomBottomSheet({ - this.roomAlias, - required this.outerContext, - this.chunk, - this.via, - super.key, - }) { - assert(roomAlias != null || chunk != null); - } - - 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 && knock - ? await client.knockRoom(chunk.roomId, serverName: via) - : await client.joinRoom( - roomAlias ?? chunk!.roomId, - serverName: via, - ); - - if (!knock && client.getRoomById(roomId) == null) { - await client.waitForRoomInSync(roomId); - } - return roomId; - }, - ); - if (knock) { - return; - } - if (result.error == null) { - Navigator.of(context).pop(true); - // don't open the room if the joined room is a space - if (chunk?.roomType != 'm.space' && - !client.getRoomById(result.result!)!.isSpace) { - outerContext.go('/rooms/${result.result!}'); - } - return; - } - } - - 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; - final roomLink = roomAlias ?? chunk?.roomId; - return SafeArea( - child: Scaffold( - appBar: AppBar( - title: Text( - chunk?.name ?? roomAlias ?? chunk?.roomId ?? 'Unknown', - overflow: TextOverflow.fade, - ), - leading: Center( - child: CloseButton( - onPressed: Navigator.of(context, rootNavigator: false).pop, - ), - ), - actions: roomAlias == null - ? null - : [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: IconButton( - icon: const Icon(Icons.qr_code_rounded), - onPressed: () => showQrCodeViewer( - context, - roomAlias, - ), - ), - ), - ], - ), - body: FutureBuilder( - future: _search(), - builder: (context, snapshot) { - final theme = Theme.of(context); - - final profile = snapshot.data; - return ListView( - padding: EdgeInsets.zero, - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: profile == null - ? const Center( - child: CircularProgressIndicator.adaptive(), - ) - : Avatar( - client: Matrix.of(outerContext).client, - mxContent: profile.avatarUrl, - name: profile.name ?? roomAlias, - size: Avatar.defaultSize * 3, - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextButton.icon( - onPressed: roomLink != null - ? () => FluffyShare.share( - roomLink, - context, - copyOnly: true, - ) - : null, - icon: const Icon( - Icons.copy_outlined, - size: 14, - ), - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.onSurface, - iconColor: theme.colorScheme.onSurface, - ), - label: Text( - roomLink ?? '...', - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - TextButton.icon( - onPressed: () {}, - icon: const Icon( - Icons.groups_3_outlined, - size: 14, - ), - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.onSurface, - iconColor: theme.colorScheme.onSurface, - ), - label: Text( - L10n.of(context).countParticipants( - profile?.numJoinedMembers ?? 0, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: ElevatedButton.icon( - onPressed: () => _joinRoom(context), - label: Text( - chunk?.joinRule == 'knock' && - Matrix.of(outerContext) - .client - .getRoomById(chunk!.roomId) == - null - ? L10n.of(context).knock - : chunk?.roomType == 'm.space' - ? L10n.of(context).joinSpace - : L10n.of(context).joinRoom, - ), - icon: const Icon(Icons.navigate_next), - ), - ), - const SizedBox(height: 16), - if (profile?.topic?.isNotEmpty ?? false) - ListTile( - subtitle: SelectableLinkify( - text: profile!.topic!, - linkStyle: TextStyle( - color: theme.colorScheme.primary, - decorationColor: theme.colorScheme.primary, - ), - style: TextStyle( - fontSize: 14, - color: theme.textTheme.bodyMedium!.color, - ), - options: const LinkifyOptions(humanize: false), - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), - ), - ), - ], - ); - }, - ), - ), - ); - } -} diff --git a/lib/widgets/room_loader.dart b/lib/widgets/room_loader.dart new file mode 100644 index 000000000..8e94f8186 --- /dev/null +++ b/lib/widgets/room_loader.dart @@ -0,0 +1,88 @@ +import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/room_from_public_rooms_chunk.dart'; +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/widgets/matrix.dart'; + +class RoomLoader extends StatelessWidget { + final String roomId; + final PublicRoomsChunk? chunk; + final Widget Function(BuildContext context, Room room) builder; + + const RoomLoader({ + required this.roomId, + required this.builder, + this.chunk, + super.key, + }); + + static final Map> _roomLoaders = {}; + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final existingRoom = client.getRoomById(roomId); + + if (existingRoom != null) { + return builder(context, existingRoom); + } + + final chunk = this.chunk; + if (chunk != null) { + return builder(context, chunk.createRoom(client)); + } + + final roomLoader = + _roomLoaders[roomId] ??= client.getRoomState(roomId).then((states) { + final room = Room( + id: roomId, + client: client, + prev_batch: '', + membership: Membership.leave, + ); + states.forEach(room.setState); + return room; + }); + + return FutureBuilder( + key: ValueKey(roomId), + future: roomLoader, + builder: (context, snapshot) { + final room = snapshot.data; + if (room != null) return builder(context, room); + final error = snapshot.error; + if (error != null) { + return Scaffold( + appBar: AppBar( + leading: Center( + child: BackButton(onPressed: Navigator.of(context).pop), + ), + ), + body: Center( + child: Column( + spacing: 8, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.search_off_outlined), + Text(error.toLocalizedString(context)), + ], + ), + ), + ); + } + return Scaffold( + appBar: AppBar( + leading: Center( + child: BackButton(onPressed: Navigator.of(context).pop), + ), + ), + body: const Center( + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ), + ); + }, + ); + } +}