// SPDX-FileCopyrightText: 2019-Present Christian Kußowski // SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:tatchat/config/app_config.dart'; import 'package:tatchat/l10n/l10n.dart'; import 'package:tatchat/pages/new_group/new_group_view.dart'; import 'package:tatchat/utils/file_selector.dart'; import 'package:tatchat/utils/matrix_live_kit_calls/matrix_live_kit_call_member.dart'; import 'package:tatchat/widgets/matrix.dart'; import 'package:go_router/go_router.dart'; import 'package:material_ui/material_ui.dart'; import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; class NewGroup extends StatefulWidget { final CreateGroupType createGroupType; final String? spaceId; const NewGroup({ this.createGroupType = CreateGroupType.group, this.spaceId, super.key, }); @override NewGroupController createState() => NewGroupController(); } class NewGroupController extends State { TextEditingController nameController = TextEditingController(); bool publicGroup = false; bool groupCanBeFound = false; Uint8List? avatar; Uri? avatarUrl; Object? error; bool loading = false; CreateGroupType get createGroupType => _createGroupType ?? widget.createGroupType; CreateGroupType? _createGroupType; void setCreateGroupType(Set b) => setState(() => _createGroupType = b.single); void setPublicGroup(bool b) => setState(() => publicGroup = groupCanBeFound = b); void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b); Future selectPhoto() async { final photo = await selectFiles( context, type: FileType.image, allowMultiple: false, ); final bytes = await photo.singleOrNull?.readAsBytes(); setState(() { avatarUrl = null; avatar = bytes; }); } Future _createGroup() async { if (!mounted) return; final client = Matrix.of(context).client; final roomId = await client.createGroupChat( visibility: groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, preset: publicGroup ? sdk.CreateRoomPreset.publicChat : sdk.CreateRoomPreset.privateChat, groupName: nameController.text.isNotEmpty ? nameController.text : null, powerLevelContentOverride: publicGroup ? null : { 'events': {MatrixRtcCallMember.eventType: 0}, }, initialState: [ if (avatar != null) sdk.StateEvent( type: sdk.EventTypes.RoomAvatar, content: {'url': avatarUrl.toString()}, ), ], ); await _addToSpace(roomId); if (!mounted) return; context.go('/rooms/$roomId/invite'); } Future _createSpace() async { if (!mounted) return; final spaceId = await Matrix.of(context).client.createRoom( preset: publicGroup ? sdk.CreateRoomPreset.publicChat : sdk.CreateRoomPreset.privateChat, creationContent: {'type': RoomCreationTypes.mSpace}, visibility: publicGroup ? sdk.Visibility.public : null, roomAliasName: publicGroup ? nameController.text.trim().toLowerCase().replaceAll(' ', '_') : null, name: nameController.text.trim(), powerLevelContentOverride: {'events_default': 100}, initialState: [ if (avatar != null) sdk.StateEvent( type: sdk.EventTypes.RoomAvatar, content: {'url': avatarUrl.toString()}, ), ], ); await _addToSpace(spaceId); if (!mounted) return; context.pop(spaceId); } Future _createCanal() async { if (!mounted) return; final client = Matrix.of(context).client; final roomId = await client.createGroupChat( visibility: groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, preset: publicGroup ? sdk.CreateRoomPreset.publicChat : sdk.CreateRoomPreset.privateChat, groupName: nameController.text.isNotEmpty ? nameController.text : null, powerLevelContentOverride: { // Only admins (power level 100) are allowed to post in a canal. 'events_default': 100, }, initialState: [ if (avatar != null) sdk.StateEvent( type: sdk.EventTypes.RoomAvatar, content: {'url': avatarUrl.toString()}, ), ], ); final canal = client.getRoomById(roomId); if (canal != null) { await canal.addTag(AppConfig.canalTag); } await _addToSpace(roomId); if (!mounted) return; context.go('/rooms/$roomId/invite'); } Future _createMural() async { if (!mounted) return; final client = Matrix.of(context).client; final roomId = await client.createGroupChat( visibility: groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, preset: publicGroup ? sdk.CreateRoomPreset.publicChat : sdk.CreateRoomPreset.privateChat, groupName: nameController.text.isNotEmpty ? nameController.text : null, powerLevelContentOverride: publicGroup ? null : { 'events': {MatrixRtcCallMember.eventType: 0}, }, initialState: [ if (avatar != null) sdk.StateEvent( type: sdk.EventTypes.RoomAvatar, content: {'url': avatarUrl.toString()}, ), ], ); final mural = client.getRoomById(roomId); if (mural != null) { await mural.addTag(AppConfig.muralTag); } await _addToSpace(roomId); if (!mounted) return; context.go('/rooms/$roomId/invite'); } 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; try { if (nameController.text.trim().isEmpty && createGroupType == CreateGroupType.space) { setState(() => error = L10n.of(context).pleaseFillOut); return; } setState(() { loading = true; error = null; }); final avatar = this.avatar; avatarUrl ??= avatar == null ? null : await client.uploadContent(avatar); if (!mounted) return; switch (createGroupType) { case CreateGroupType.group: await _createGroup(); case CreateGroupType.space: await _createSpace(); case CreateGroupType.canal: await _createCanal(); case CreateGroupType.mural: await _createMural(); } } catch (e, s) { sdk.Logs().d('Unable to create group', e, s); setState(() { error = e; loading = false; }); } } @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 void dispose() { nameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) => NewGroupView(this); } enum CreateGroupType { group, space, canal, mural }