// SPDX-FileCopyrightText: 2019-Present Christian Kußowski // SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:async'; import 'package:material_ui/material_ui.dart'; import 'package:matrix/matrix.dart'; import 'package:tatchat/l10n/l10n.dart'; import 'package:tatchat/pages/chat_list/chat_list.dart'; import 'package:tatchat/pages/chat_list/mural_list_item.dart'; import 'package:tatchat/pages/chat_list/tabs/chat_tab_header.dart'; import 'package:tatchat/pages/chat_list/tabs/empty_tab_view.dart'; import 'package:tatchat/pages/chat_list/tabs/home_tab.dart'; import 'package:tatchat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:tatchat/utils/matrix_sdk_extensions/room_extension.dart'; import 'package:tatchat/utils/stream_extension.dart'; import 'package:tatchat/widgets/matrix.dart'; class MuralsTab extends StatefulWidget { final ChatListController controller; const MuralsTab(this.controller, {super.key}); @override State createState() => _MuralsTabState(); } class _MuralsTabState extends State { final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); final Set _loadingRooms = {}; bool _isSearchMode = false; @override void dispose() { _searchController.dispose(); _searchFocusNode.dispose(); super.dispose(); } /// Loads the missing state events (e.g. `m.room.topic` and `m.room.create`) /// of a mural room so that its description and creator are already visible in /// the list without having to open the mural first. Future _loadMissingRoomState(Room room) async { final roomId = room.id; try { final stateEvents = await room.client.getRoomState(roomId); for (final state in stateEvents) { room.setState(StrippedStateEvent.fromJson(state.toJson())); } if (!mounted) return; setState(() {}); } catch (_) { // State loading is best-effort, ignore failures (e.g. offline). } finally { _loadingRooms.remove(roomId); } } @override Widget build(BuildContext context) { final client = Matrix.of(context).client; final theme = Theme.of(context); final searchText = _searchController.text.trim().toLowerCase(); return ChatTabScaffold( tab: HomeTab.murals, isSearchMode: _isSearchMode, onSearchPressed: () => setState(() => _isSearchMode = true), onSearchCancelled: () { setState(() { _isSearchMode = false; _searchController.clear(); }); }, searchController: _searchController, searchFocusNode: _searchFocusNode, onSearchChanged: (_) => setState(() {}), controller: widget.controller, child: StreamBuilder( key: ValueKey(client.userID.toString()), stream: client.onSync.stream .where((s) => s.hasRoomUpdate) .rateLimit(const Duration(seconds: 1)), builder: (context, _) { final murals = client.rooms .where((room) => !room.isLeftOrBanned) .where((room) => room.isMural) .where( (room) => searchText.isEmpty || room .getLocalizedDisplayname(MatrixLocals(L10n.of(context))) .toLowerCase() .contains(searchText), ) .toList(); return ColoredBox( color: theme.colorScheme.surface, child: CustomScrollView( slivers: [ if (murals.isEmpty) SliverFillRemaining( hasScrollBody: false, child: EmptyTabView( icon: Icons.dashboard_outlined, title: L10n.of(context).noMuralsFound, ), ) else SliverList.builder( itemCount: murals.length, itemBuilder: (context, i) { final room = murals[i]; final roomId = room.id; if ((room.topic.isEmpty || room.creatorUserIds.isEmpty) && !_loadingRooms.contains(roomId)) { _loadingRooms.add(roomId); unawaited(_loadMissingRoomState(room)); } final creatorId = room.creatorUserIds.isEmpty ? null : room.creatorUserIds.first; final creatorState = creatorId == null ? null : room.getState(EventTypes.RoomMember, creatorId); final creatorDisplayName = creatorState?.content .tryGet('displayname'); return MuralListItem( room, key: Key('mural_list_item_$roomId'), onTap: () => widget.controller.onChatTap(room), onLongPress: (context) => widget.controller.chatContextAction(room, context), activeChat: widget.controller.activeChat == room.id, displayAvatar: Uri.tryParse( creatorState?.content.tryGet('avatar_url') ?? '', ), displayAvatarName: creatorDisplayName, creatorName: creatorDisplayName ?? creatorId, description: room.topic, ); }, ), ], ), ); }, ), ); } }