From 2538f31351cf0fbd94d1cf6583bcc07f9241de5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sun, 19 Oct 2025 12:10:14 +0200 Subject: [PATCH] design: Improved spaces UX --- lib/l10n/intl_en.arb | 7 +- lib/pages/chat_list/chat_list_item.dart | 42 +- lib/pages/chat_list/space_view.dart | 484 ++++++++++++------ lib/pages/chat_list/unread_bubble.dart | 56 ++ .../adaptive_dialogs/public_room_dialog.dart | 4 +- lib/widgets/avatar.dart | 10 +- 6 files changed, 410 insertions(+), 193 deletions(-) create mode 100644 lib/pages/chat_list/unread_bubble.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1d9e551ad..f08cb8fa0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3378,5 +3378,10 @@ "noMessagesYet": "No messages yet", "longPressToRecordVoiceMessage": "Long press to record voice message.", "pause": "Pause", - "resume": "Resume" + "resume": "Resume", + "newSubSpace": "New sub space", + "moveToDifferentSpace": "Move to different space", + "moveUp": "Move up", + "moveDown": "Move down", + "removeFromSpaceDescription": "The chat will be removed from the space but still appear in your chat list." } diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 73000ccd9..6a3ea9582 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -4,6 +4,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pages/chat_list/unread_bubble.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/room_status_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; @@ -46,11 +47,6 @@ class ChatListItem extends StatelessWidget { final unread = room.isUnread; final directChatMatrixId = room.directChatMatrixID; final isDirectChat = directChatMatrixId != null; - final unreadBubbleSize = unread || room.hasNewMessages - ? room.notificationCount > 0 - ? 20.0 - : 14.0 - : 0.0; final hasNotifications = room.notificationCount > 0; final backgroundColor = activeChat ? theme.colorScheme.secondaryContainer : null; @@ -318,41 +314,7 @@ class ChatListItem extends StatelessWidget { ), ), const SizedBox(width: 8), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 7), - height: unreadBubbleSize, - width: !hasNotifications && !unread && !room.hasNewMessages - ? 0 - : (unreadBubbleSize - 9) * - room.notificationCount.toString().length + - 9, - decoration: BoxDecoration( - color: room.highlightCount > 0 - ? theme.colorScheme.error - : hasNotifications || room.markedUnread - ? theme.colorScheme.primary - : theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(7), - ), - child: hasNotifications - ? Text( - room.notificationCount.toString(), - style: TextStyle( - color: room.highlightCount > 0 - ? theme.colorScheme.onError - : hasNotifications - ? theme.colorScheme.onPrimary - : theme.colorScheme.onPrimaryContainer, - fontSize: 13, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ) - : const SizedBox.shrink(), - ), + UnreadBubble(room: room), ], ), onTap: onTap, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 33ea75344..d9f47938c 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; @@ -8,20 +10,31 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/pages/chat_list/search_title.dart'; +import 'package:fluffychat/pages/chat_list/unread_bubble.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/public_room_dialog.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; 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/hover_builder.dart'; import 'package:fluffychat/widgets/matrix.dart'; enum AddRoomType { chat, subspace } +enum SpaceChildAction { edit, moveToSpace, removeFromSpace } + +enum SpaceActions { + settings, + invite, + members, + leave, +} + class SpaceView extends StatefulWidget { final String spaceId; final void Function() onBack; @@ -58,9 +71,28 @@ class _SpaceViewState extends State { } void _loadHierarchy() async { - final room = Matrix.of(context).client.getRoomById(widget.spaceId); + final matrix = Matrix.of(context); + final room = matrix.client.getRoomById(widget.spaceId); if (room == null) return; + final cacheKey = 'spaces_history_cache${room.id}'; + if (_discoveredChildren.isEmpty) { + final cachedChildren = matrix.store.getStringList(cacheKey); + if (cachedChildren != null) { + try { + _discoveredChildren.addAll( + cachedChildren.map( + (jsonString) => + SpaceRoomsChunk$2.fromJson(jsonDecode(jsonString)), + ), + ); + } catch (e, s) { + Logs().e('Unable to json decode spaces hierarchy cache!', e, s); + matrix.store.remove(cacheKey); + } + } + } + setState(() { _isLoading = true; }); @@ -74,16 +106,25 @@ class _SpaceViewState extends State { ); if (!mounted) return; setState(() { + if (_nextBatch == null) _discoveredChildren.clear(); _nextBatch = hierarchy.nextBatch; if (hierarchy.nextBatch == null) { _noMoreRooms = true; } _discoveredChildren.addAll( - hierarchy.rooms - .where((c) => room.client.getRoomById(c.roomId) == null), + hierarchy.rooms.where((room) => room.roomId != widget.spaceId), ); _isLoading = false; }); + + if (_nextBatch == null) { + matrix.store.setStringList( + cacheKey, + _discoveredChildren + .map((child) => jsonEncode(child.toJson())) + .toList(), + ); + } } catch (e, s) { Logs().w('Unable to load hierarchy', e, s); if (!mounted) return; @@ -111,9 +152,7 @@ class _SpaceViewState extends State { ), ); if (mounted && joined == true) { - setState(() { - _discoveredChildren.remove(item); - }); + setState(() {}); } } @@ -129,6 +168,10 @@ class _SpaceViewState extends State { await space?.postLoad(); context.push('/rooms/${widget.spaceId}/invite'); break; + case SpaceActions.members: + await space?.postLoad(); + context.push('/rooms/${widget.spaceId}/details/members'); + break; case SpaceActions.leave: final confirmed = await showOkCancelAlertDialog( context: context, @@ -151,27 +194,11 @@ class _SpaceViewState extends State { } } - void _addChatOrSubspace() async { - final roomType = await showModalActionPopup( - context: context, - title: L10n.of(context).addChatOrSubSpace, - actions: [ - AdaptiveModalAction( - value: AddRoomType.subspace, - label: L10n.of(context).createNewSpace, - ), - AdaptiveModalAction( - value: AddRoomType.chat, - label: L10n.of(context).createGroup, - ), - ], - ); - if (roomType == null) return; - + void _addChatOrSubspace(AddRoomType roomType) async { final names = await showTextInputDialog( context: context, title: roomType == AddRoomType.subspace - ? L10n.of(context).createNewSpace + ? L10n.of(context).newSubSpace : L10n.of(context).createGroup, hintText: roomType == AddRoomType.subspace ? L10n.of(context).spaceName @@ -196,29 +223,169 @@ class _SpaceViewState extends State { late final String roomId; final activeSpace = client.getRoomById(widget.spaceId)!; await activeSpace.postLoad(); + final isPublicSpace = activeSpace.joinRules == JoinRules.public; if (roomType == AddRoomType.subspace) { roomId = await client.createSpace( name: names, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, + visibility: + isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private, ); } else { roomId = await client.createGroupChat( + enableEncryption: !isPublicSpace, groupName: names, - preset: activeSpace.joinRules == JoinRules.public + preset: isPublicSpace ? CreateRoomPreset.publicChat : CreateRoomPreset.privateChat, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, + visibility: + isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private, + initialState: isPublicSpace + ? null + : [ + StateEvent( + content: { + 'join_rule': 'restricted', + 'allow': [ + { + 'room_id': widget.spaceId, + 'type': 'm.room_membership', + }, + ], + }, + type: EventTypes.RoomJoinRules, + ), + ], ); } await activeSpace.setSpaceChild(roomId); }, ); if (result.error != null) return; + setState(() { + _nextBatch = null; + _discoveredChildren.clear(); + }); + _loadHierarchy(); + } + + void _showSpaceChildEditMenu(BuildContext posContext, String roomId) async { + final overlay = + Overlay.of(posContext).context.findRenderObject() as RenderBox; + + final button = posContext.findRenderObject() as RenderBox; + + final position = RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(const Offset(0, -65), ancestor: overlay), + button.localToGlobal( + button.size.bottomRight(Offset.zero) + const Offset(-50, 0), + ancestor: overlay, + ), + ), + Offset.zero & overlay.size, + ); + + final action = await showMenu( + context: posContext, + position: position, + items: [ + PopupMenuItem( + value: SpaceChildAction.moveToSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.move_down_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).moveToDifferentSpace), + ], + ), + ), + PopupMenuItem( + value: SpaceChildAction.edit, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.edit_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).edit), + ], + ), + ), + PopupMenuItem( + value: SpaceChildAction.removeFromSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_remove_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).removeFromSpace), + ], + ), + ), + ], + ); + if (action == null) return; + if (!mounted) return; + final space = Matrix.of(context).client.getRoomById(widget.spaceId); + if (space == null) return; + switch (action) { + case SpaceChildAction.edit: + context.push('/rooms/${widget.spaceId}/details'); + case SpaceChildAction.moveToSpace: + final spacesWithPowerLevels = space.client.rooms + .where( + (room) => + room.isSpace && + room.canChangeStateEvent(EventTypes.SpaceChild) && + room.id != widget.spaceId, + ) + .toList(); + final newSpace = await showModalActionPopup( + context: context, + title: L10n.of(context).space, + actions: spacesWithPowerLevels + .map( + (space) => AdaptiveModalAction( + value: space, + label: space + .getLocalizedDisplayname(MatrixLocals(L10n.of(context))), + ), + ) + .toList(), + ); + if (newSpace == null) return; + final result = await showFutureLoadingDialog( + context: context, + future: () async { + await newSpace.setSpaceChild(newSpace.id); + await space.removeSpaceChild(roomId); + }, + ); + if (result.isError) return; + if (!mounted) return; + _nextBatch = null; + _loadHierarchy(); + return; + + case SpaceChildAction.removeFromSpace: + final consent = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context).removeFromSpace, + message: L10n.of(context).removeFromSpaceDescription, + ); + if (consent != OkCancelResult.ok) return; + if (!mounted) return; + final result = await showFutureLoadingDialog( + context: context, + future: () => space.removeSpaceChild(roomId), + ); + if (result.isError) return; + if (!mounted) return; + _nextBatch = null; + _loadHierarchy(); + return; + } } @override @@ -228,6 +395,11 @@ class _SpaceViewState extends State { final room = Matrix.of(context).client.getRoomById(widget.spaceId); final displayname = room?.getLocalizedDisplayname() ?? L10n.of(context).nothingFound; + const avatarSize = Avatar.defaultSize / 1.5; + final isAdmin = room?.canChangeStateEvent( + EventTypes.SpaceChild, + ) == + true; return Scaffold( appBar: AppBar( leading: FluffyThemes.isColumnMode(context) @@ -242,6 +414,7 @@ class _SpaceViewState extends State { title: ListTile( contentPadding: EdgeInsets.zero, leading: Avatar( + size: avatarSize, mxContent: room?.avatar, name: displayname, border: BorderSide(width: 1, color: theme.dividerColor), @@ -252,18 +425,38 @@ class _SpaceViewState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, ), - subtitle: room == null - ? null - : Text( - L10n.of(context).countChatsAndCountParticipants( - room.spaceChildren.length, - room.summary.mJoinedMemberCount ?? 1, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), ), actions: [ + if (isAdmin) + PopupMenuButton( + icon: const Icon(Icons.add_outlined), + onSelected: _addChatOrSubspace, + tooltip: L10n.of(context).addChatOrSubSpace, + itemBuilder: (context) => [ + PopupMenuItem( + value: AddRoomType.chat, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).newGroup), + ], + ), + ), + PopupMenuItem( + value: AddRoomType.subspace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.workspaces_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).newSubSpace), + ], + ), + ), + ], + ), PopupMenuButton( useRootNavigator: true, onSelected: _onSpaceAction, @@ -290,6 +483,21 @@ class _SpaceViewState extends State { ], ), ), + PopupMenuItem( + value: SpaceActions.members, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_outlined), + const SizedBox(width: 12), + Text( + L10n.of(context).countParticipants( + room?.summary.mJoinedMemberCount ?? 1, + ), + ), + ], + ), + ), PopupMenuItem( value: SpaceActions.leave, child: Row( @@ -305,16 +513,6 @@ class _SpaceViewState extends State { ), ], ), - floatingActionButton: room?.canChangeStateEvent( - EventTypes.SpaceChild, - ) == - true - ? FloatingActionButton.extended( - onPressed: _addChatOrSubspace, - label: Text(L10n.of(context).group), - icon: const Icon(Icons.group_add_outlined), - ) - : null, body: room == null ? const Center( child: Icon( @@ -332,9 +530,11 @@ class _SpaceViewState extends State { .whereType() .toSet(); - final joinedRooms = room.client.rooms - .where((room) => childrenIds.remove(room.id)) - .toList(); + final joinedRooms = Map.fromEntries( + room.client.rooms + .where((room) => childrenIds.remove(room.id)) + .map((room) => MapEntry(room.id, room)), + ); final joinedParents = room.spaceParents .map((parent) { @@ -349,7 +549,6 @@ class _SpaceViewState extends State { slivers: [ SliverAppBar( floating: true, - toolbarHeight: 72, scrolledUnderElevation: 0, backgroundColor: Colors.transparent, automaticallyImplyLeading: false, @@ -359,11 +558,6 @@ class _SpaceViewState extends State { textInputAction: TextInputAction.search, decoration: InputDecoration( filled: true, - fillColor: theme.colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), contentPadding: EdgeInsets.zero, hintText: L10n.of(context).search, hintStyle: TextStyle( @@ -423,42 +617,11 @@ class _SpaceViewState extends State { }, ), SliverList.builder( - itemCount: joinedRooms.length, + itemCount: _discoveredChildren.length + 1, itemBuilder: (context, i) { - final joinedRoom = joinedRooms[i]; - return ChatListItem( - joinedRoom, - filter: filter, - onTap: () => widget.onChatTab(joinedRoom), - onLongPress: (context) => widget.onChatContext( - joinedRoom, - context, - ), - activeChat: widget.activeChat == joinedRoom.id, - ); - }, - ), - SliverList.builder( - itemCount: _discoveredChildren.length + 2, - itemBuilder: (context, i) { - if (i == 0) { - return SearchTitle( - title: L10n.of(context).discover, - icon: const Icon(Icons.explore_outlined), - ); - } - i--; if (i == _discoveredChildren.length) { if (_noMoreRooms) { - return Padding( - padding: const EdgeInsets.all(12.0), - child: Center( - child: Text( - L10n.of(context).noMoreChatsFound, - style: const TextStyle(fontSize: 13), - ), - ), - ); + return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.symmetric( @@ -468,11 +631,7 @@ class _SpaceViewState extends State { child: TextButton( onPressed: _isLoading ? null : _loadHierarchy, child: _isLoading - ? LinearProgressIndicator( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ) + ? const CircularProgressIndicator.adaptive() : Text(L10n.of(context).loadMore), ), ); @@ -484,6 +643,7 @@ class _SpaceViewState extends State { if (!displayname.toLowerCase().contains(filter)) { return const SizedBox.shrink(); } + final joinedRoom = joinedRooms[item.roomId]; return Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -493,51 +653,83 @@ class _SpaceViewState extends State { borderRadius: BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge, - child: ListTile( - visualDensity: - const VisualDensity(vertical: -0.5), - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - onTap: () => _joinChildRoom(item), - leading: Avatar( - mxContent: item.avatarUrl, - name: displayname, - borderRadius: item.roomType == 'm.space' - ? BorderRadius.circular( - AppConfig.borderRadius / 2, - ) + color: joinedRoom != null && + widget.activeChat == joinedRoom.id + ? theme.colorScheme.secondaryContainer + : Colors.transparent, + child: HoverBuilder( + builder: (context, hovered) => ListTile( + visualDensity: + const VisualDensity(vertical: -0.5), + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + onTap: joinedRoom != null + ? () => widget.onChatTab(joinedRoom) + : () => _joinChildRoom(item), + onLongPress: isAdmin + ? () => _showSpaceChildEditMenu( + context, + item.roomId, + ) : null, - ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - item.numJoinedMembers.toString(), - style: TextStyle( - fontSize: 13, - color: theme.textTheme.bodyMedium!.color, - ), - ), - const SizedBox(width: 4), - const Icon( - Icons.people_outlined, - size: 14, - ), - ], - ), - subtitle: Text( - item.topic ?? - L10n.of(context).countParticipants( - item.numJoinedMembers, + leading: hovered && isAdmin + ? SizedBox.square( + dimension: avatarSize, + child: IconButton( + splashRadius: avatarSize, + iconSize: 14, + style: IconButton.styleFrom( + foregroundColor: theme.colorScheme + .onTertiaryContainer, + backgroundColor: theme + .colorScheme.tertiaryContainer, + ), + onPressed: () => + _showSpaceChildEditMenu( + context, + item.roomId, + ), + icon: const Icon(Icons.edit_outlined), + ), + ) + : Avatar( + size: avatarSize, + mxContent: item.avatarUrl, + name: '#', + backgroundColor: + theme.colorScheme.surfaceContainer, + textColor: item.name?.darkColor ?? + theme.colorScheme.onSurface, + border: item.roomType == 'm.space' + ? BorderSide( + color: theme.colorScheme + .surfaceContainerHighest, + ) + : null, + borderRadius: item.roomType == 'm.space' + ? BorderRadius.circular( + AppConfig.borderRadius / 4, + ) + : null, + ), + title: Row( + children: [ + Expanded( + child: Opacity( + opacity: joinedRoom == null ? 0.5 : 1, + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + if (joinedRoom != null) + UnreadBubble(room: joinedRoom) + else + const Icon(Icons.chevron_right_outlined), + ], + ), ), ), ), @@ -552,9 +744,3 @@ class _SpaceViewState extends State { ); } } - -enum SpaceActions { - settings, - invite, - leave, -} diff --git a/lib/pages/chat_list/unread_bubble.dart b/lib/pages/chat_list/unread_bubble.dart new file mode 100644 index 000000000..964094f97 --- /dev/null +++ b/lib/pages/chat_list/unread_bubble.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/themes.dart'; + +class UnreadBubble extends StatelessWidget { + final Room room; + const UnreadBubble({required this.room, super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final unread = room.isUnread; + final hasNotifications = room.notificationCount > 0; + final unreadBubbleSize = unread || room.hasNewMessages + ? room.notificationCount > 0 + ? 20.0 + : 14.0 + : 0.0; + return AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 7), + height: unreadBubbleSize, + width: !hasNotifications && !unread && !room.hasNewMessages + ? 0 + : (unreadBubbleSize - 9) * room.notificationCount.toString().length + + 9, + decoration: BoxDecoration( + color: room.highlightCount > 0 + ? theme.colorScheme.error + : hasNotifications || room.markedUnread + ? theme.colorScheme.primary + : theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(7), + ), + child: hasNotifications + ? Text( + room.notificationCount.toString(), + style: TextStyle( + color: room.highlightCount > 0 + ? theme.colorScheme.onError + : hasNotifications + ? theme.colorScheme.onPrimary + : theme.colorScheme.onPrimaryContainer, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ) + : const SizedBox.shrink(), + ); + } +} diff --git a/lib/widgets/adaptive_dialogs/public_room_dialog.dart b/lib/widgets/adaptive_dialogs/public_room_dialog.dart index 09e125613..add44c89d 100644 --- a/lib/widgets/adaptive_dialogs/public_room_dialog.dart +++ b/lib/widgets/adaptive_dialogs/public_room_dialog.dart @@ -30,7 +30,9 @@ class PublicRoomDialog extends StatelessWidget { final result = await showFutureLoadingDialog( context: context, future: () async { - if (chunk != null && client.getRoomById(chunk.roomId) != null) { + if (chunk != null && + client.getRoomById(chunk.roomId) != null && + client.getRoomById(chunk.roomId)?.membership != Membership.leave) { return chunk.roomId; } final roomId = chunk != null && knock diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 31a523fa3..fdecdb5d7 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -18,6 +18,8 @@ class Avatar extends StatelessWidget { final BorderRadius? borderRadius; final IconData? icon; final BorderSide? border; + final Color? backgroundColor; + final Color? textColor; const Avatar({ this.mxContent, @@ -30,6 +32,8 @@ class Avatar extends StatelessWidget { this.borderRadius, this.border, this.icon, + this.backgroundColor, + this.textColor, super.key, }); @@ -71,14 +75,16 @@ class Avatar extends StatelessWidget { height: size, placeholder: (_) => noPic ? Container( - decoration: BoxDecoration(color: name?.lightColorAvatar), + decoration: BoxDecoration( + color: backgroundColor ?? name?.lightColorAvatar, + ), alignment: Alignment.center, child: Text( fallbackLetters, textAlign: TextAlign.center, style: TextStyle( fontFamily: 'RobotoMono', - color: Colors.white, + color: textColor ?? Colors.white, fontWeight: FontWeight.bold, fontSize: (size / 2.5).roundToDouble(), ),