feat: redesign of space access page (#2903)
This commit is contained in:
parent
f7a8ef9afd
commit
249538c20b
28 changed files with 617 additions and 618 deletions
|
|
@ -4962,5 +4962,18 @@
|
|||
"access": "Access",
|
||||
"addSubspace": "Add subspace",
|
||||
"botSettings": "Bot settings",
|
||||
"activitySuggestionTimeoutMessage": "We are working hard to generate activties for you, please check back in a minute"
|
||||
"activitySuggestionTimeoutMessage": "We are working hard to generate activties for you, please check back in a minute",
|
||||
"accessSettingsWarning": "Oops! It looks like you don't have permission to set the Access rules of this room. You should check these to make sure they're what you need and talk to a room admin if you need to change them",
|
||||
"howSpaceCanBeFound": "How this space can be found",
|
||||
"private": "Private",
|
||||
"cannotBeFoundInSearch": "Cannot be found in search",
|
||||
"public": "Public",
|
||||
"visibleToCommunity": "Visible to the broader Pangea Chat community via \"Find your people\"",
|
||||
"howSpaceCanBeJoined": "How this space can be joined",
|
||||
"restricted": "Restricted",
|
||||
"canBeFoundVia": "Can be found via:",
|
||||
"canBeFoundViaInvitation": "\u2022 invitation",
|
||||
"canBeFoundViaCodeOrLink": "\u2022 code or link",
|
||||
"canBeFoundViaKnock": "\u2022 request to join and admin approval",
|
||||
"anyoneCanJoin": "Anyone can join! However, admin can kick and ban whoever misbehaves. Those who are banned may not return!"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,6 @@ abstract class AppRoutes {
|
|||
activeChat: state.pathParameters['roomid'],
|
||||
// #Pangea
|
||||
activeSpaceId: state.uri.queryParameters['spaceId'],
|
||||
activeFilter: state.uri.queryParameters['filter'],
|
||||
// Pangea#
|
||||
displayNavigationRail:
|
||||
state.path?.startsWith('/rooms/settings') != true,
|
||||
|
|
@ -245,7 +244,6 @@ abstract class AppRoutes {
|
|||
activeChat: state.pathParameters['roomid'],
|
||||
// #Pangea
|
||||
activeSpaceId: state.uri.queryParameters['spaceId'],
|
||||
activeFilter: state.uri.queryParameters['filter'],
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
|
|
@ -287,22 +285,12 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const NewGroup(),
|
||||
// #Pangea
|
||||
// const NewGroup(),
|
||||
NewGroup(spaceId: state.uri.queryParameters['space']),
|
||||
// Pangea#
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
// #Pangea
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':spaceid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
NewGroup(spaceId: state.pathParameters['spaceid']!),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
// Pangea#
|
||||
),
|
||||
GoRoute(
|
||||
path: 'newspace',
|
||||
|
|
@ -775,7 +763,6 @@ abstract class AppRoutes {
|
|||
mainView: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpaceId: state.uri.queryParameters['spaceId'],
|
||||
activeFilter: state.uri.queryParameters['filter'],
|
||||
displayNavigationRail:
|
||||
state.path?.startsWith('/rooms/settings') != true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -901,7 +901,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
pangeaEditingEvent = previousEdit;
|
||||
}
|
||||
|
||||
final spaceCode = room.classCode(context);
|
||||
final spaceCode = room.classCode;
|
||||
if (spaceCode != null) {
|
||||
GoogleAnalytics.sendMessage(
|
||||
room.id,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_page.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/pages/pangea_chat_access_settings.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/client_spaces_extension.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
|
|
@ -58,13 +59,22 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
|||
}
|
||||
|
||||
void setJoinRule(JoinRules? newJoinRules) async {
|
||||
if (newJoinRules == null) return;
|
||||
// #Pangea
|
||||
// if (newJoinRules == null) return;
|
||||
if (newJoinRules == null || room.joinRules == newJoinRules) return;
|
||||
// Pangea#
|
||||
setState(() {
|
||||
joinRulesLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await room.setJoinRules(newJoinRules);
|
||||
// #Pangea
|
||||
// await room.setJoinRules(newJoinRules);
|
||||
await room.client.pangeaSetJoinRules(
|
||||
room.id,
|
||||
newJoinRules.toString().replaceAll('JoinRules.', ''),
|
||||
);
|
||||
// Pangea#
|
||||
} catch (e, s) {
|
||||
Logs().w('Unable to change join rules', e, s);
|
||||
if (mounted) {
|
||||
|
|
@ -295,6 +305,9 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChatAccessSettingsPageView(this);
|
||||
// #Pangea
|
||||
// return ChatAccessSettingsPageView(this);
|
||||
return PangeaChatAccessSettingsPageView(this);
|
||||
// Pangea#
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,20 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/settings/settings.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/pages/pangea_chat_details.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/download_chat.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/download_file.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/set_class_name.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/space_code.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
|
|
@ -209,70 +211,6 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
// Pangea#
|
||||
|
||||
// #Pangea
|
||||
bool showEditNameIcon = false;
|
||||
void hoverEditNameIcon(bool hovering) =>
|
||||
setState(() => showEditNameIcon = !showEditNameIcon);
|
||||
|
||||
Future<void> setJoinRules(JoinRules joinRules) async {
|
||||
if (roomId == null) return;
|
||||
final room = Matrix.of(context).client.getRoomById(roomId!);
|
||||
if (room == null) return;
|
||||
|
||||
final content = room.getState(EventTypes.RoomJoinRules)?.content ?? {};
|
||||
content['join_rule'] = joinRules.toString().replaceAll('JoinRules.', '');
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await room.client.setRoomStateWithKey(
|
||||
roomId!,
|
||||
EventTypes.RoomJoinRules,
|
||||
'',
|
||||
content,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setVisibility(sdk.Visibility visibility) async {
|
||||
if (roomId == null) return;
|
||||
final room = Matrix.of(context).client.getRoomById(roomId!);
|
||||
if (room == null) return;
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await room.client.setRoomVisibilityOnDirectory(
|
||||
room.id,
|
||||
visibility: visibility,
|
||||
);
|
||||
},
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> toggleMute() async {
|
||||
final client = Matrix.of(context).client;
|
||||
final Room? room = client.getRoomById(roomId!);
|
||||
if (room == null) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await (room.pushRuleState == PushRuleState.notify
|
||||
? room.setPushRuleState(PushRuleState.mentionsOnly)
|
||||
: room.setPushRuleState(PushRuleState.notify));
|
||||
},
|
||||
);
|
||||
|
||||
// wait for push rule update in sync
|
||||
await client.onSync.stream.firstWhere(
|
||||
(sync) =>
|
||||
sync.accountData != null &&
|
||||
sync.accountData!.isNotEmpty &&
|
||||
sync.accountData!.any((e) => e.type == 'm.push_rules'),
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void downloadChatAction() async {
|
||||
if (roomId == null) return;
|
||||
final Room? room = Matrix.of(context).client.getRoomById(roomId!);
|
||||
|
|
@ -389,22 +327,37 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
);
|
||||
if (names == null) return;
|
||||
final client = Matrix.of(context).client;
|
||||
final result = await showFutureLoadingDialog(
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final activeSpace = client.getRoomById(roomId!)!;
|
||||
await activeSpace.postLoad();
|
||||
final accessCode = await SpaceCodeUtil.generateSpaceCode(client);
|
||||
|
||||
final resp = await client.createSpace(
|
||||
final resp = await client.createRoom(
|
||||
name: names,
|
||||
visibility: activeSpace.joinRules == JoinRules.public
|
||||
? sdk.Visibility.public
|
||||
: sdk.Visibility.private,
|
||||
visibility: RoomDefaults.spaceChildVisibility,
|
||||
creationContent: {'type': 'm.space'},
|
||||
initialState: [
|
||||
RoomDefaults.defaultSpacePowerLevels(client.userID!),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomJoinRules,
|
||||
content: {
|
||||
'join_rule': 'knock_restricted',
|
||||
'allow': [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": roomId!,
|
||||
}
|
||||
],
|
||||
ModelKey.accessCode: accessCode,
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
await activeSpace.pangeaSetSpaceChild(resp);
|
||||
await activeSpace.addToSpace(resp);
|
||||
},
|
||||
);
|
||||
if (result.error != null) return;
|
||||
}
|
||||
// Pangea#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import 'package:fluffychat/pangea/chat_settings/widgets/delete_space_dialog.dart
|
|||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/client_spaces_extension.dart';
|
||||
import 'package:fluffychat/pangea/subscription/widgets/subscription_snackbar.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
|
|
@ -83,7 +84,6 @@ class ChatList extends StatefulWidget {
|
|||
final String? activeChat;
|
||||
// #Pangea
|
||||
final String? activeSpaceId;
|
||||
final String? activeFilter;
|
||||
// Pangea#
|
||||
final bool displayNavigationRail;
|
||||
|
||||
|
|
@ -92,7 +92,6 @@ class ChatList extends StatefulWidget {
|
|||
required this.activeChat,
|
||||
// #Pangea
|
||||
this.activeSpaceId,
|
||||
this.activeFilter,
|
||||
// Pangea#
|
||||
this.displayNavigationRail = false,
|
||||
});
|
||||
|
|
@ -535,7 +534,7 @@ class ChatListController extends State<ChatList>
|
|||
// #Pangea
|
||||
final String? justInputtedCode =
|
||||
MatrixState.pangeaController.classController.justInputtedCode();
|
||||
final newSpaceCode = space?.classCode(context);
|
||||
final newSpaceCode = space?.classCode;
|
||||
if (newSpaceCode?.toLowerCase() == justInputtedCode?.toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -625,12 +624,6 @@ class ChatListController extends State<ChatList>
|
|||
|
||||
_activeSpaceId =
|
||||
widget.activeSpaceId == 'clear' ? null : widget.activeSpaceId;
|
||||
|
||||
if (widget.activeFilter == 'groups') {
|
||||
activeFilter = AppConfig.separateChatTypes
|
||||
? ActiveFilter.groups
|
||||
: ActiveFilter.allChats;
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
super.initState();
|
||||
|
|
@ -640,15 +633,6 @@ class ChatListController extends State<ChatList>
|
|||
@override
|
||||
void didUpdateWidget(ChatList oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.activeFilter != oldWidget.activeFilter &&
|
||||
widget.activeFilter == 'groups') {
|
||||
setActiveFilter(
|
||||
AppConfig.separateChatTypes
|
||||
? ActiveFilter.groups
|
||||
: ActiveFilter.allChats,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.activeSpaceId != oldWidget.activeSpaceId &&
|
||||
widget.activeSpaceId != null) {
|
||||
widget.activeSpaceId == 'clear'
|
||||
|
|
@ -822,12 +806,11 @@ class ChatListController extends State<ChatList>
|
|||
],
|
||||
),
|
||||
),
|
||||
if (spacesWithPowerLevels.isNotEmpty
|
||||
// #Pangea
|
||||
&&
|
||||
!room.isSpace
|
||||
// Pangea#
|
||||
)
|
||||
// #Pangea
|
||||
// if (spacesWithPowerLevels.isNotEmpty)
|
||||
if (spacesWithPowerLevels.isNotEmpty &&
|
||||
room.canChangeStateEvent(EventTypes.SpaceParent))
|
||||
// Pangea#
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.addToSpace,
|
||||
child: Row(
|
||||
|
|
@ -846,8 +829,11 @@ class ChatListController extends State<ChatList>
|
|||
// if the room has a parent for which the user has a high enough power level
|
||||
// to set parent's space child events, show option to remove the room from the space
|
||||
if (room.spaceParents.isNotEmpty &&
|
||||
room.canChangeStateEvent(EventTypes.SpaceParent) &&
|
||||
room.pangeaSpaceParents.any(
|
||||
(r) => r.canChangeStateEvent(EventTypes.SpaceChild),
|
||||
(r) =>
|
||||
r.canChangeStateEvent(EventTypes.SpaceChild) &&
|
||||
r.id == activeSpaceId,
|
||||
) &&
|
||||
activeSpaceId != null)
|
||||
PopupMenuItem(
|
||||
|
|
@ -1022,21 +1008,40 @@ class ChatListController extends State<ChatList>
|
|||
context: context,
|
||||
// #Pangea
|
||||
// future: () => space.setSpaceChild(room.id),
|
||||
future: () => space.pangeaSetSpaceChild(room.id),
|
||||
future: () => space.addToSpace(room.id),
|
||||
// Pangea#
|
||||
);
|
||||
// #Pangea
|
||||
try {
|
||||
await space.client.setSpaceChildAccess(room.id);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context).accessSettingsWarning),
|
||||
duration: const Duration(seconds: 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
case ChatContextAction.removeFromSpace:
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final futures = room.pangeaSpaceParents
|
||||
.where((r) => r.canChangeStateEvent(EventTypes.SpaceChild))
|
||||
.map((space) => removeSpaceChild(space, room.id));
|
||||
await Future.wait(futures);
|
||||
final activeSpace = room.client.getRoomById(activeSpaceId!);
|
||||
if (activeSpace == null) return;
|
||||
await activeSpace.removeSpaceChild(room.id);
|
||||
},
|
||||
);
|
||||
try {
|
||||
await room.client.resetSpaceChildAccess(room.id);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context).accessSettingsWarning),
|
||||
duration: const Duration(seconds: 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
case ChatContextAction.delete:
|
||||
if (room.isSpace) {
|
||||
|
|
@ -1077,22 +1082,6 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
/// Remove a room from a space. Often, the user will have permission to set
|
||||
/// the SpaceChild event for the parent space, but not the SpaceParent event.
|
||||
/// This would cause a permissions error, but the child will still be removed
|
||||
/// via the SpaceChild event. If that's the case, silence the error.
|
||||
Future<void> removeSpaceChild(Room space, String roomId) async {
|
||||
try {
|
||||
await space.removeSpaceChild(roomId);
|
||||
} catch (err) {
|
||||
if ((err as MatrixException).error != MatrixError.M_FORBIDDEN) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void dismissStatusList() async {
|
||||
final result = await showOkCancelAlertDialog(
|
||||
title: L10n.of(context).hidePresences,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/widgets/chat_list_view_body_wrapper.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/space_floating_actions_buttons.dart';
|
||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||
|
||||
class ChatListView extends StatelessWidget {
|
||||
|
|
@ -58,30 +57,17 @@ class ChatListView extends StatelessWidget {
|
|||
// body: ChatListViewBody(controller),
|
||||
body: ChatListViewBodyWrapper(controller: controller),
|
||||
// Pangea#
|
||||
floatingActionButton:
|
||||
// #Pangea
|
||||
// !controller.isSearchMode && controller.activeSpaceId == null
|
||||
controller.activeFilter == ActiveFilter.spaces &&
|
||||
controller.activeSpaceId == null &&
|
||||
!controller.isSearchMode
|
||||
? const SpaceFloatingActionButtons()
|
||||
: !controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
// Pangea#
|
||||
? FloatingActionButton.extended(
|
||||
// #Pangea
|
||||
// onPressed: () => context.go('/rooms/newprivatechat'),
|
||||
onPressed: () => context.go(
|
||||
'/rooms/newgroup/${controller.activeSpaceId ?? ''}',
|
||||
),
|
||||
// Pangea#
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
label: Text(
|
||||
L10n.of(context).chat,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
floatingActionButton: !controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: () => context.go('/rooms/newprivatechat'),
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
label: Text(
|
||||
L10n.of(context).chat,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -678,7 +678,8 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// #Pangea
|
||||
// onPressed: _addChatOrSubspace,
|
||||
// label: Text(L10n.of(context).group),
|
||||
onPressed: () => context.go("/rooms/newgroup/${widget.spaceId}"),
|
||||
onPressed: () =>
|
||||
context.go("/rooms/newgroup?space=${widget.spaceId}"),
|
||||
label: Text(L10n.of(context).chat),
|
||||
// Pangea#
|
||||
icon: const Icon(Icons.group_add_outlined),
|
||||
|
|
|
|||
|
|
@ -54,17 +54,17 @@ class InvitationSelectionView extends StatelessWidget {
|
|||
title: Text(L10n.of(context).inviteContact),
|
||||
// #Pangea
|
||||
actions: [
|
||||
if (room.isSpace && room.classCode(context) != null)
|
||||
if (room.isSpace && room.classCode != null)
|
||||
PopupMenuButton<int>(
|
||||
icon: const Icon(Icons.share_outlined),
|
||||
onSelected: (value) async {
|
||||
final spaceCode = room.classCode(context)!;
|
||||
final spaceCode = room.classCode!;
|
||||
String toCopy = spaceCode;
|
||||
if (value == 0) {
|
||||
final String initialUrl =
|
||||
kIsWeb ? html.window.origin! : Environment.frontendURL;
|
||||
toCopy =
|
||||
"$initialUrl/#/join_with_link?${SpaceConstants.classCode}=${room.classCode(context)}";
|
||||
"$initialUrl/#/join_with_link?${SpaceConstants.classCode}=${room.classCode}";
|
||||
}
|
||||
|
||||
await Clipboard.setData(ClipboardData(text: toCopy));
|
||||
|
|
@ -92,8 +92,7 @@ class InvitationSelectionView extends StatelessWidget {
|
|||
child: ListTile(
|
||||
leading: const Icon(Icons.share_outlined),
|
||||
title: Text(
|
||||
L10n.of(context)
|
||||
.shareInviteCode(room.classCode(context)!),
|
||||
L10n.of(context).shareInviteCode(room.classCode!),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -140,11 +140,22 @@ class NewGroupController extends State<NewGroup> {
|
|||
content: {'url': avatarUrl.toString()},
|
||||
),
|
||||
// #Pangea
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(Matrix.of(context).client.userID!),
|
||||
RoomDefaults.defaultPowerLevels(
|
||||
Matrix.of(context).client.userID!,
|
||||
),
|
||||
if (widget.spaceId != null)
|
||||
StateEvent(
|
||||
type: EventTypes.RoomJoinRules,
|
||||
content: {
|
||||
'join_rule': 'knock_restricted',
|
||||
'allow': [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": widget.spaceId,
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
// #Pangea
|
||||
|
|
@ -164,7 +175,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
if (widget.spaceId != null) {
|
||||
try {
|
||||
final space = client.getRoomById(widget.spaceId!);
|
||||
await space?.pangeaSetSpaceChild(room.id);
|
||||
await space?.addToSpace(room.id);
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
e: "Failed to add room to space",
|
||||
|
|
@ -187,7 +198,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
);
|
||||
}
|
||||
}
|
||||
context.go('/rooms/$roomId/invite?filter=groups');
|
||||
context.go('/rooms/$roomId/invite');
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +242,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
|
||||
final room = Matrix.of(context).client.getRoomById(spaceId);
|
||||
if (room == null) return;
|
||||
final spaceCode = room.classCode(context);
|
||||
final spaceCode = room.classCode;
|
||||
if (spaceCode != null) {
|
||||
GoogleAnalytics.createClass(room.name, spaceCode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,27 +65,26 @@ class NewGroupView extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<CreateGroupType>(
|
||||
selected: {controller.createGroupType},
|
||||
onSelectionChanged: controller.setCreateGroupType,
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: CreateGroupType.group,
|
||||
// #Pangea
|
||||
// label: Text(L10n.of(context).group),
|
||||
label: Text(L10n.of(context).chat),
|
||||
// Pangea#
|
||||
),
|
||||
ButtonSegment(
|
||||
value: CreateGroupType.space,
|
||||
label: Text(L10n.of(context).space),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// #Pangea
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: SegmentedButton<CreateGroupType>(
|
||||
// selected: {controller.createGroupType},
|
||||
// onSelectionChanged: controller.setCreateGroupType,
|
||||
// segments: [
|
||||
// ButtonSegment(
|
||||
// value: CreateGroupType.group,
|
||||
// label: Text(L10n.of(context).group),
|
||||
// ),
|
||||
// ButtonSegment(
|
||||
// value: CreateGroupType.space,
|
||||
// label: Text(L10n.of(context).space),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 16),
|
||||
// Pangea#
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: controller.loading ? null : controller.selectPhoto,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:pretty_qr_code/pretty_qr_code.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -33,13 +32,15 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
leading: const Center(child: BackButton()),
|
||||
title: Text(L10n.of(context).newChat),
|
||||
backgroundColor: theme.scaffoldBackgroundColor,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
UrlLauncher(context, AppConfig.startChatTutorial).launchUrl,
|
||||
child: Text(L10n.of(context).help),
|
||||
),
|
||||
],
|
||||
// #Pangea
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// onPressed:
|
||||
// UrlLauncher(context, AppConfig.startChatTutorial).launchUrl,
|
||||
// child: Text(L10n.of(context).help),
|
||||
// ),
|
||||
// ],
|
||||
// Pangea#
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
|
|
@ -138,15 +139,17 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
title: Text(L10n.of(context).shareInviteLink),
|
||||
onTap: controller.inviteAction,
|
||||
),
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.tertiaryContainer,
|
||||
foregroundColor: theme.colorScheme.onTertiaryContainer,
|
||||
child: const Icon(Icons.group_add_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).createGroup),
|
||||
onTap: () => context.go('/rooms/newgroup'),
|
||||
),
|
||||
// #Pangea
|
||||
// ListTile(
|
||||
// leading: CircleAvatar(
|
||||
// backgroundColor: theme.colorScheme.tertiaryContainer,
|
||||
// foregroundColor: theme.colorScheme.onTertiaryContainer,
|
||||
// child: const Icon(Icons.group_add_outlined),
|
||||
// ),
|
||||
// title: Text(L10n.of(context).createGroup),
|
||||
// onTap: () => context.go('/rooms/newgroup'),
|
||||
// ),
|
||||
// Pangea#
|
||||
if (PlatformInfos.isMobile)
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
|
|
@ -181,7 +184,10 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
constraints:
|
||||
const BoxConstraints(maxWidth: 256),
|
||||
child: PrettyQrView.data(
|
||||
data: 'https://matrix.to/#/$userId',
|
||||
// #Pangea
|
||||
// data: 'https://matrix.to/#/$userId',
|
||||
data: Environment.frontendURL,
|
||||
// Pangea#
|
||||
decoration: PrettyQrDecoration(
|
||||
shape: PrettyQrSmoothSymbol(
|
||||
roundFactor: 1,
|
||||
|
|
|
|||
|
|
@ -179,12 +179,8 @@ class ActivityRoomSelectionState extends State<ActivityRoomSelection> {
|
|||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(
|
||||
Matrix.of(context).client.userID!,
|
||||
),
|
||||
RoomDefaults.defaultPowerLevels(
|
||||
Matrix.of(context).client.userID!,
|
||||
),
|
||||
if (avatar != null && avatarUrl != null)
|
||||
StateEvent(
|
||||
|
|
|
|||
|
|
@ -1,60 +1,78 @@
|
|||
Map<String, dynamic> defaultPowerLevels(String userID) => {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
Map<String, dynamic> restrictedPowerLevels(String userID) => {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 50,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
class RoomDefaults {
|
||||
static StateEvent defaultPowerLevels(String userID) => StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> defaultSpacePowerLevels(String userID) => {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.join_rules": 100,
|
||||
"m.space.child": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
static StateEvent restrictedPowerLevels(String userID) => StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 50,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
static StateEvent defaultSpacePowerLevels(String userID) => StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.join_rules": 100,
|
||||
"m.space.child": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
static Visibility spaceChildVisibility = Visibility.private;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ void chatListHandleSpaceTap(
|
|||
if (rooms.any((s) => s.spaceChildren.any((c) => c.roomId == space.id))) {
|
||||
autoJoin(space);
|
||||
} else if (justInputtedCode != null &&
|
||||
justInputtedCode == space.classCode(context)) {
|
||||
justInputtedCode == space.classCode) {
|
||||
// do nothing
|
||||
} else {
|
||||
controller.showInviteDialog(space);
|
||||
|
|
|
|||
221
lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart
Normal file
221
lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
import 'package:flutter/material.dart' hide Visibility;
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
|
||||
class PangeaChatAccessSettingsPageView extends StatelessWidget {
|
||||
final ChatAccessSettingsController controller;
|
||||
const PangeaChatAccessSettingsPageView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = controller.room;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: const Center(child: BackButton()),
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.shield_outlined),
|
||||
const SizedBox(width: 8),
|
||||
Text(L10n.of(context).access),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
showBorder: false,
|
||||
child: StreamBuilder<Object>(
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == controller.room.id),
|
||||
builder: (context, snapshot) {
|
||||
return Container(
|
||||
width: 400.0,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: room.client.getRoomVisibilityOnDirectory(room.id),
|
||||
builder: (context, snapshot) {
|
||||
return Column(
|
||||
spacing: 16.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ChatAccessTitle(
|
||||
icon: Icons.search_outlined,
|
||||
title: L10n.of(context).howSpaceCanBeFound,
|
||||
),
|
||||
ChatAccessTile(
|
||||
emoji: "🏡",
|
||||
title: L10n.of(context).private,
|
||||
description: L10n.of(context).cannotBeFoundInSearch,
|
||||
selected: snapshot.data == Visibility.private,
|
||||
onTap: () {
|
||||
if (snapshot.data == Visibility.private) return;
|
||||
controller.setChatVisibilityOnDirectory(false);
|
||||
},
|
||||
),
|
||||
ChatAccessTile(
|
||||
emoji: "🌏",
|
||||
title: L10n.of(context).public,
|
||||
description: L10n.of(context).visibleToCommunity,
|
||||
selected: snapshot.data == Visibility.public,
|
||||
onTap: () {
|
||||
if (snapshot.data == Visibility.public) return;
|
||||
controller.setChatVisibilityOnDirectory(true);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
ChatAccessTitle(
|
||||
icon: Icons.key_outlined,
|
||||
title: L10n.of(context).howSpaceCanBeJoined,
|
||||
),
|
||||
ChatAccessTile(
|
||||
emoji: "🤝",
|
||||
title: L10n.of(context).restricted,
|
||||
descriptionWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(L10n.of(context).canBeFoundVia),
|
||||
Text(L10n.of(context).canBeFoundViaInvitation),
|
||||
Text(L10n.of(context).canBeFoundViaCodeOrLink),
|
||||
Text(L10n.of(context).canBeFoundViaKnock),
|
||||
],
|
||||
),
|
||||
selected: room.joinRules == JoinRules.knock,
|
||||
onTap: () => controller.setJoinRule(JoinRules.knock),
|
||||
),
|
||||
ChatAccessTile(
|
||||
emoji: "👐",
|
||||
title: L10n.of(context).open,
|
||||
description: L10n.of(context).anyoneCanJoin,
|
||||
selected: room.joinRules == JoinRules.public,
|
||||
onTap: () => controller.setJoinRule(JoinRules.public),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatAccessTitle extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
|
||||
const ChatAccessTitle({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: isColumnMode ? 32.0 : 24.0,
|
||||
),
|
||||
SizedBox(width: isColumnMode ? 32.0 : 16.0),
|
||||
Text(
|
||||
title,
|
||||
style: isColumnMode
|
||||
? theme.textTheme.titleLarge
|
||||
: theme.textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatAccessTile extends StatelessWidget {
|
||||
final String emoji;
|
||||
final String title;
|
||||
|
||||
final String? description;
|
||||
final Widget? descriptionWidget;
|
||||
|
||||
final bool selected;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const ChatAccessTile({
|
||||
super.key,
|
||||
required this.emoji,
|
||||
required this.title,
|
||||
this.description,
|
||||
this.descriptionWidget,
|
||||
this.selected = false,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Opacity(
|
||||
opacity: selected ? 1.0 : 0.5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.colorScheme.outline,
|
||||
width: 2,
|
||||
),
|
||||
color: selected
|
||||
? theme.colorScheme.primaryContainer.withAlpha(50)
|
||||
: null,
|
||||
),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
emoji,
|
||||
style: isColumnMode
|
||||
? theme.textTheme.displayMedium
|
||||
: theme.textTheme.displaySmall,
|
||||
),
|
||||
const SizedBox(width: 16.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: isColumnMode
|
||||
? theme.textTheme.titleLarge
|
||||
: theme.textTheme.titleMedium,
|
||||
),
|
||||
description != null
|
||||
? Text(description!)
|
||||
: descriptionWidget != null
|
||||
? descriptionWidget!
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -330,7 +330,7 @@ class RoomDetailsButtonRowState extends State<RoomDetailsButtonRow> {
|
|||
title: l10n.access,
|
||||
icon: const Icon(Icons.shield_outlined),
|
||||
onPressed: () => context.go('/rooms/${room.id}/details/access'),
|
||||
visible: room.isSpace,
|
||||
visible: room.isSpace && room.spaceParents.isEmpty,
|
||||
enabled: room.isSpace && room.isRoomAdmin,
|
||||
),
|
||||
ButtonDetails(
|
||||
|
|
@ -364,7 +364,7 @@ class RoomDetailsButtonRowState extends State<RoomDetailsButtonRow> {
|
|||
icon: const Icon(Icons.add_outlined),
|
||||
onPressed: widget.controller.addSubspace,
|
||||
visible: room.isSpace &&
|
||||
room.canSendEvent(
|
||||
room.canChangeStateEvent(
|
||||
EventTypes.SpaceChild,
|
||||
),
|
||||
showInMainView: false,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class SpaceInviteButtonsController extends State<SpaceInviteButtons> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final spaceCode = widget.room.classCode(context);
|
||||
final spaceCode = widget.room.classCode;
|
||||
if (!widget.room.isSpace || spaceCode == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class VisibilityToggle extends StatefulWidget {
|
||||
final Room room;
|
||||
final Color? iconColor;
|
||||
final Future<void> Function(matrix.Visibility) setVisibility;
|
||||
final Future<void> Function(JoinRules) setJoinRules;
|
||||
|
||||
const VisibilityToggle({
|
||||
required this.setVisibility,
|
||||
required this.setJoinRules,
|
||||
required this.room,
|
||||
this.iconColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<VisibilityToggle> createState() => VisibilityToggleState();
|
||||
}
|
||||
|
||||
class VisibilityToggleState extends State<VisibilityToggle> {
|
||||
Room get room => widget.room;
|
||||
|
||||
bool get _isPublic => room.joinRules == matrix.JoinRules.public;
|
||||
|
||||
matrix.Visibility? _visibility;
|
||||
bool _loading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getVisibility();
|
||||
}
|
||||
|
||||
Future<void> _getVisibility() async {
|
||||
try {
|
||||
final resp = await Matrix.of(context).client.getRoomVisibilityOnDirectory(
|
||||
room.id,
|
||||
);
|
||||
_visibility = resp ?? matrix.Visibility.private;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setVisibility(matrix.Visibility visibility) async {
|
||||
try {
|
||||
await widget.setVisibility(visibility);
|
||||
_visibility = visibility;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SwitchListTile.adaptive(
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
title: Text(
|
||||
L10n.of(context).requireCodeToJoin,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
secondary: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: widget.iconColor,
|
||||
child: const Icon(Icons.key_outlined),
|
||||
),
|
||||
value: !_isPublic,
|
||||
onChanged: (value) =>
|
||||
widget.setJoinRules(value ? JoinRules.knock : JoinRules.public),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).canFindInSearch,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: widget.iconColor,
|
||||
child: const Icon(Icons.search_outlined),
|
||||
),
|
||||
onTap: _visibility != null
|
||||
? () => showFutureLoadingDialog(
|
||||
future: () async {
|
||||
_setVisibility(
|
||||
_visibility == matrix.Visibility.public
|
||||
? matrix.Visibility.private
|
||||
: matrix.Visibility.public,
|
||||
);
|
||||
},
|
||||
context: context,
|
||||
)
|
||||
: null,
|
||||
trailing: _loading || _error != null
|
||||
? SizedBox(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
child: _error != null
|
||||
? Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
)
|
||||
: const CircularProgressIndicator.adaptive(),
|
||||
)
|
||||
: Switch.adaptive(
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
value: _visibility == matrix.Visibility.public,
|
||||
onChanged: (value) => showFutureLoadingDialog(
|
||||
future: () async {
|
||||
_setVisibility(
|
||||
value
|
||||
? matrix.Visibility.public
|
||||
: matrix.Visibility.private,
|
||||
);
|
||||
},
|
||||
context: context,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -269,12 +269,8 @@ class PangeaController {
|
|||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(
|
||||
matrixState.client.userID!,
|
||||
),
|
||||
RoomDefaults.defaultPowerLevels(
|
||||
matrixState.client.userID!,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:html_unescape/html_unescape.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix/src/utils/markdown.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ extension ChildrenAndParentsRoomExtension on Room {
|
|||
/// Wrapper around call to setSpaceChild with added functionality
|
||||
/// to prevent adding one room to multiple spaces, and resets the
|
||||
/// subspace's JoinRules and Visibility to defaults.
|
||||
Future<void> pangeaSetSpaceChild(
|
||||
Future<void> addToSpace(
|
||||
String roomId, {
|
||||
bool? suggested,
|
||||
}) async {
|
||||
|
|
@ -38,11 +38,9 @@ extension ChildrenAndParentsRoomExtension on Room {
|
|||
}
|
||||
|
||||
try {
|
||||
await setSpaceChild(roomId, suggested: suggested);
|
||||
await child.setJoinRules(JoinRules.public);
|
||||
await child.client.setRoomVisibilityOnDirectory(
|
||||
await _trySetSpaceChild(
|
||||
roomId,
|
||||
visibility: matrix.Visibility.private,
|
||||
suggested: suggested,
|
||||
);
|
||||
} catch (err, stack) {
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -57,6 +55,31 @@ extension ChildrenAndParentsRoomExtension on Room {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _trySetSpaceChild(
|
||||
String roomId, {
|
||||
bool? suggested,
|
||||
int retries = 0,
|
||||
}) async {
|
||||
final Room? child = client.getRoomById(roomId);
|
||||
if (child == null) return;
|
||||
|
||||
try {
|
||||
await setSpaceChild(roomId, suggested: suggested);
|
||||
} catch (err) {
|
||||
retries++;
|
||||
if (retries < 3) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return _trySetSpaceChild(
|
||||
roomId,
|
||||
suggested: suggested,
|
||||
retries: retries,
|
||||
);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A map of child suggestion status for a space.
|
||||
Map<String, bool> get spaceChildSuggestionStatus {
|
||||
if (!isSpace) return {};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
part of "pangea_room_extension.dart";
|
||||
|
||||
extension SpaceRoomExtension on Room {
|
||||
String? classCode(BuildContext context) {
|
||||
if (!isSpace) {
|
||||
for (final Room potentialClassRoom in pangeaSpaceParents) {
|
||||
if (potentialClassRoom.isSpace) {
|
||||
return SpaceRoomExtension(potentialClassRoom).classCode(context);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String? get classCode {
|
||||
if (!isSpace) return null;
|
||||
final roomJoinRules = getState(EventTypes.RoomJoinRules, "");
|
||||
if (roomJoinRules != null) {
|
||||
final accessCode = roomJoinRules.content.tryGet(ModelKey.accessCode);
|
||||
|
|
|
|||
|
|
@ -122,10 +122,18 @@ extension SpacesClientExtension on Client {
|
|||
type: EventTypes.RoomAvatar,
|
||||
content: {'url': introChatUploadURL.toString()},
|
||||
),
|
||||
RoomDefaults.defaultPowerLevels(userID!),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(userID!),
|
||||
type: EventTypes.RoomJoinRules,
|
||||
content: {
|
||||
'join_rule': 'knock_restricted',
|
||||
'allow': [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": space.id,
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -142,10 +150,18 @@ extension SpacesClientExtension on Client {
|
|||
type: EventTypes.RoomAvatar,
|
||||
content: {'url': announcementsChatUploadURL.toString()},
|
||||
),
|
||||
RoomDefaults.restrictedPowerLevels(userID!),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: restrictedPowerLevels(userID!),
|
||||
type: EventTypes.RoomJoinRules,
|
||||
content: {
|
||||
'join_rule': 'knock_restricted',
|
||||
'allow': [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": space.id,
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -166,11 +182,11 @@ extension SpacesClientExtension on Client {
|
|||
}
|
||||
}
|
||||
|
||||
final addIntroChatFuture = space.pangeaSetSpaceChild(
|
||||
final addIntroChatFuture = space.addToSpace(
|
||||
roomIds[0],
|
||||
);
|
||||
|
||||
final addAnnouncementsChatFuture = space.pangeaSetSpaceChild(
|
||||
final addAnnouncementsChatFuture = space.addToSpace(
|
||||
roomIds[1],
|
||||
);
|
||||
|
||||
|
|
@ -186,11 +202,7 @@ extension SpacesClientExtension on Client {
|
|||
required JoinRules joinRules,
|
||||
}) {
|
||||
return [
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultSpacePowerLevels(userID),
|
||||
),
|
||||
RoomDefaults.defaultSpacePowerLevels(userID),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomJoinRules,
|
||||
content: {
|
||||
|
|
@ -200,4 +212,69 @@ extension SpacesClientExtension on Client {
|
|||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// Keep the room's current join rule state event content (except for what's intentionally replaced)
|
||||
/// since space's access codes were stored there. Don't want to accidentally remove them.
|
||||
Future<void> pangeaSetJoinRules(
|
||||
String roomId,
|
||||
String joinRule, {
|
||||
List<Map<String, dynamic>>? allow,
|
||||
}) async {
|
||||
final room = getRoomById(roomId);
|
||||
if (room == null) {
|
||||
throw Exception('Room not found for user ID: $userID');
|
||||
}
|
||||
|
||||
final currentJoinRule = room
|
||||
.getState(
|
||||
EventTypes.RoomJoinRules,
|
||||
)
|
||||
?.content ??
|
||||
{};
|
||||
|
||||
if (currentJoinRule[ModelKey.joinRule] == joinRule &&
|
||||
(currentJoinRule['allow'] == allow)) {
|
||||
return; // No change needed
|
||||
}
|
||||
|
||||
currentJoinRule[ModelKey.joinRule] = joinRule;
|
||||
currentJoinRule['allow'] = allow;
|
||||
|
||||
await setRoomStateWithKey(
|
||||
roomId,
|
||||
EventTypes.RoomJoinRules,
|
||||
'',
|
||||
currentJoinRule,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setSpaceChildAccess(String roomId) async {
|
||||
await pangeaSetJoinRules(
|
||||
roomId,
|
||||
'knock_restricted',
|
||||
allow: [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": id,
|
||||
}
|
||||
],
|
||||
);
|
||||
|
||||
await setRoomVisibilityOnDirectory(
|
||||
roomId,
|
||||
visibility: Visibility.private,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> resetSpaceChildAccess(String roomId) async {
|
||||
await pangeaSetJoinRules(
|
||||
roomId,
|
||||
JoinRules.knock.toString().replaceAll('JoinRules.', ''),
|
||||
);
|
||||
|
||||
await setRoomVisibilityOnDirectory(
|
||||
roomId,
|
||||
visibility: Visibility.private,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class AddRoomDialog extends StatefulWidget {
|
||||
const AddRoomDialog({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
AddRoomDialogState createState() => AddRoomDialogState();
|
||||
}
|
||||
|
||||
class AddRoomDialogState extends State<AddRoomDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _roomNameController = TextEditingController();
|
||||
final TextEditingController _roomDescriptionController =
|
||||
TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_roomNameController.dispose();
|
||||
_roomDescriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
L10n.of(context).createChat,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _roomNameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).chatName,
|
||||
),
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
maxLength: 64,
|
||||
validator: (text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return L10n.of(context).pleaseChoose;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _roomDescriptionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).chatDescription,
|
||||
),
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
maxLength: 255,
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(null);
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final isValid = _formKey.currentState!.validate();
|
||||
if (!isValid) return;
|
||||
Navigator.of(context).pop(
|
||||
RoomResponse(
|
||||
roomName: _roomNameController.text,
|
||||
roomDescription: _roomDescriptionController.text,
|
||||
joinRules: JoinRules.public,
|
||||
visibility: matrix.Visibility.private,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(L10n.of(context).confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RoomResponse {
|
||||
final String roomName;
|
||||
final String roomDescription;
|
||||
final JoinRules joinRules;
|
||||
final matrix.Visibility visibility;
|
||||
|
||||
RoomResponse({
|
||||
required this.roomName,
|
||||
required this.roomDescription,
|
||||
required this.joinRules,
|
||||
required this.visibility,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'roomName': roomName,
|
||||
'roomDescripion': roomDescription,
|
||||
'joinRules': joinRules,
|
||||
'visibility': visibility,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import '../widgets/matrix.dart';
|
||||
|
||||
|
|
@ -34,10 +35,13 @@ abstract class FluffyShare {
|
|||
final client = Matrix.of(context).client;
|
||||
final ownProfile = await client.fetchOwnProfile();
|
||||
await FluffyShare.share(
|
||||
L10n.of(context).inviteText(
|
||||
ownProfile.displayName ?? client.userID!,
|
||||
'https://matrix.to/#/${client.userID}?client=im.fluffychat',
|
||||
),
|
||||
// #Pangea
|
||||
// L10n.of(context).inviteText(
|
||||
// ownProfile.displayName ?? client.userID!,
|
||||
// 'https://matrix.to/#/${client.userID}?client=im.fluffychat',
|
||||
// ),
|
||||
"${ownProfile.displayName ?? client.userID!} invited you to Pangea Chat.\nOpen the invite link: \n ${Environment.frontendURL}",
|
||||
// Pangea#
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,9 @@ class UserDialog extends StatelessWidget {
|
|||
bigButtons: true,
|
||||
onPressed: () async {
|
||||
final router = GoRouter.of(context);
|
||||
Navigator.of(context).pop();
|
||||
// #Pangea
|
||||
// Navigator.of(context).pop();
|
||||
// Pangea#
|
||||
final roomIdResult = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
// #Pangea
|
||||
|
|
@ -194,6 +196,9 @@ class UserDialog extends StatelessWidget {
|
|||
),
|
||||
// Pangea#
|
||||
);
|
||||
// #Pangea
|
||||
Navigator.of(context).pop();
|
||||
// Pangea#
|
||||
final roomId = roomIdResult.result;
|
||||
if (roomId == null) return;
|
||||
router.go('/rooms/$roomId');
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Future<Result<T>> showFutureLoadingDialog<T>({
|
|||
ExceptionContext? exceptionContext,
|
||||
bool ignoreError = false,
|
||||
// #Pangea
|
||||
String? Function(Object, StackTrace?)? onError,
|
||||
Object? Function(Object, StackTrace?)? onError,
|
||||
String? Function()? onSuccess,
|
||||
VoidCallback? onDismiss,
|
||||
// Pangea#
|
||||
|
|
@ -82,7 +82,7 @@ class LoadingDialog<T> extends StatefulWidget {
|
|||
final Future<T> future;
|
||||
final ExceptionContext? exceptionContext;
|
||||
// #Pangea
|
||||
final String? Function(Object, StackTrace?)? onError;
|
||||
final Object? Function(Object, StackTrace?)? onError;
|
||||
final String? Function()? onSuccess;
|
||||
final VoidCallback? onDismiss;
|
||||
// Pangea#
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue