refactor: Better UX for create space children

This commit is contained in:
Christian Kußowski 2026-02-28 15:52:57 +01:00
parent 0061c948cd
commit 9fd85a8b58
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
3 changed files with 76 additions and 116 deletions

View file

@ -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,
),

View file

@ -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<SpaceView> {
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<void> _loadHierarchy() async {
@override
void dispose() {
_childStateSub?.cancel();
super.dispose();
}
Future<void> _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<SpaceView> {
}
}
Future<void> _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<void> _showSpaceChildEditMenu(
BuildContext posContext,
String roomId,
@ -363,9 +301,17 @@ class _SpaceViewState extends State<SpaceView> {
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<SpaceView> {
if (result.isError) return;
if (!mounted) return;
_nextBatch = null;
_loadHierarchy();
return;
case SpaceChildAction.mute:
await showFutureLoadingDialog(
@ -455,34 +400,11 @@ class _SpaceViewState extends State<SpaceView> {
),
actions: [
if (isAdmin)
PopupMenuButton<AddRoomType>(
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<SpaceActions>(
useRootNavigator: true,

View file

@ -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<NewGroup> {
Future<void> _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<NewGroup> {
),
],
);
await _addToSpace(roomId);
if (!mounted) return;
context.go('/rooms/$roomId/invite');
}
@ -104,10 +113,23 @@ class NewGroupController extends State<NewGroup> {
),
],
);
await _addToSpace(spaceId);
if (!mounted) return;
context.pop<String>(spaceId);
}
Future<void> _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<void> submitAction([_]) async {
final client = Matrix.of(context).client;
@ -143,6 +165,16 @@ class NewGroupController extends State<NewGroup> {
}
}
@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);
}