From 9fd85a8b584887bb134ecbce1d5b012a818feab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sat, 28 Feb 2026 15:52:57 +0100 Subject: [PATCH] refactor: Better UX for create space children --- lib/config/routes.dart | 12 ++- lib/pages/chat_list/space_view.dart | 144 +++++++--------------------- lib/pages/new_group/new_group.dart | 36 ++++++- 3 files changed, 76 insertions(+), 116 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 451b1b665..a6d2d70ce 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -176,8 +176,11 @@ abstract class AppRoutes { ), GoRoute( path: 'newgroup', - pageBuilder: (context, state) => - defaultPageBuilder(context, state, const NewGroup()), + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + NewGroup(spaceId: state.uri.queryParameters['space_id']), + ), redirect: loggedOutRedirect, ), GoRoute( @@ -185,7 +188,10 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - const NewGroup(createGroupType: CreateGroupType.space), + NewGroup( + createGroupType: CreateGroupType.space, + spaceId: state.uri.queryParameters['space_id'], + ), ), redirect: loggedOutRedirect, ), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 81352560d..72c9446a4 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; @@ -15,14 +16,11 @@ import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/utils/string_color.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 { mute, unmute, @@ -59,13 +57,30 @@ class _SpaceViewState extends State { bool _noMoreRooms = false; bool _isLoading = false; + StreamSubscription? _childStateSub; + @override void initState() { _loadHierarchy(); + _childStateSub = Matrix.of(context).client.onSync.stream + .where( + (syncUpdate) => + syncUpdate.rooms?.join?[widget.spaceId]?.timeline?.events?.any( + (event) => event.type == EventTypes.SpaceChild, + ) ?? + false, + ) + .listen(_loadHierarchy); super.initState(); } - Future _loadHierarchy() async { + @override + void dispose() { + _childStateSub?.cancel(); + super.dispose(); + } + + Future _loadHierarchy([_]) async { final matrix = Matrix.of(context); final room = matrix.client.getRoomById(widget.spaceId); if (room == null) return; @@ -189,83 +204,6 @@ class _SpaceViewState extends State { } } - Future _addChatOrSubspace(AddRoomType roomType) async { - final names = await showTextInputDialog( - context: context, - title: roomType == AddRoomType.subspace - ? L10n.of(context).newSubSpace - : L10n.of(context).createGroup, - hintText: roomType == AddRoomType.subspace - ? L10n.of(context).spaceName - : L10n.of(context).groupName, - minLines: 1, - maxLines: 1, - maxLength: 64, - validator: (text) { - if (text.isEmpty) { - return L10n.of(context).pleaseChoose; - } - return null; - }, - okLabel: L10n.of(context).create, - cancelLabel: L10n.of(context).cancel, - ); - if (names == null) return; - final client = Matrix.of(context).client; - final result = await showFutureLoadingDialog( - context: context, - future: () async { - 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: isPublicSpace - ? sdk.Visibility.public - : sdk.Visibility.private, - ); - } else { - roomId = await client.createGroupChat( - enableEncryption: !isPublicSpace, - groupName: names, - preset: isPublicSpace - ? CreateRoomPreset.publicChat - : CreateRoomPreset.privateChat, - 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(); - } - Future _showSpaceChildEditMenu( BuildContext posContext, String roomId, @@ -363,9 +301,17 @@ class _SpaceViewState extends State { child: Row( mainAxisSize: .min, children: [ - const Icon(Icons.remove), + Icon( + Icons.remove, + color: Theme.of(context).colorScheme.onErrorContainer, + ), const SizedBox(width: 12), - Text(L10n.of(context).removeFromSpace), + Text( + L10n.of(context).removeFromSpace, + style: TextStyle( + color: Theme.of(context).colorScheme.onErrorContainer, + ), + ), ], ), ), @@ -389,7 +335,6 @@ class _SpaceViewState extends State { if (result.isError) return; if (!mounted) return; _nextBatch = null; - _loadHierarchy(); return; case SpaceChildAction.mute: await showFutureLoadingDialog( @@ -455,34 +400,11 @@ class _SpaceViewState extends State { ), actions: [ if (isAdmin) - PopupMenuButton( - icon: const Icon(Icons.add_outlined), - onSelected: _addChatOrSubspace, + IconButton( + icon: Icon(Icons.add_outlined), tooltip: L10n.of(context).addChatOrSubSpace, - itemBuilder: (context) => [ - PopupMenuItem( - value: AddRoomType.chat, - child: Row( - 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: .min, - children: [ - const Icon(Icons.workspaces_outlined), - const SizedBox(width: 12), - Text(L10n.of(context).newSubSpace), - ], - ), - ), - ], + onPressed: () => + context.go('/rooms/newgroup?space_id=${widget.spaceId}'), ), PopupMenuButton( useRootNavigator: true, diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 51202f2be..f1945e470 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -14,7 +14,12 @@ import 'package:fluffychat/widgets/matrix.dart'; class NewGroup extends StatefulWidget { final CreateGroupType createGroupType; - const NewGroup({this.createGroupType = CreateGroupType.group, super.key}); + final String? spaceId; + const NewGroup({ + this.createGroupType = CreateGroupType.group, + this.spaceId, + super.key, + }); @override NewGroupController createState() => NewGroupController(); @@ -63,7 +68,9 @@ class NewGroupController extends State { Future _createGroup() async { if (!mounted) return; - final roomId = await Matrix.of(context).client.createGroupChat( + final client = Matrix.of(context).client; + + final roomId = await client.createGroupChat( visibility: groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, @@ -79,7 +86,9 @@ class NewGroupController extends State { ), ], ); + await _addToSpace(roomId); if (!mounted) return; + context.go('/rooms/$roomId/invite'); } @@ -104,10 +113,23 @@ class NewGroupController extends State { ), ], ); + await _addToSpace(spaceId); if (!mounted) return; context.pop(spaceId); } + Future _addToSpace(String roomId) async { + final spaceId = widget.spaceId; + if (spaceId != null) { + final activeSpace = Matrix.of(context).client.getRoomById(spaceId); + if (activeSpace == null) { + throw Exception('Can not add group to space: Space not found $spaceId'); + } + await activeSpace.postLoad(); + await activeSpace.setSpaceChild(roomId); + } + } + Future submitAction([_]) async { final client = Matrix.of(context).client; @@ -143,6 +165,16 @@ class NewGroupController extends State { } } + @override + void initState() { + final spaceId = widget.spaceId; + if (spaceId != null) { + final space = Matrix.of(context).client.getRoomById(spaceId); + publicGroup = space?.joinRules == JoinRules.public; + } + super.initState(); + } + @override Widget build(BuildContext context) => NewGroupView(this); }