Space Settings UI tweaks (#4678)

* design tweaks for the space settings flow

* pr suggestions

* update proj
This commit is contained in:
Mauro
2025-10-31 12:52:56 +01:00
committed by GitHub
parent 010500a083
commit dc9ab56972
37 changed files with 187 additions and 40 deletions
@@ -91,6 +91,10 @@ extension AccessibilityTests {
try await performAccessibilityAudit(named: "ComposerToolbar_Previews") try await performAccessibilityAudit(named: "ComposerToolbar_Previews")
} }
func testCopyTextButton() async throws {
try await performAccessibilityAudit(named: "CopyTextButton_Previews")
}
func testCreateRoom() async throws { func testCreateRoom() async throws {
try await performAccessibilityAudit(named: "CreateRoom_Previews") try await performAccessibilityAudit(named: "CreateRoom_Previews")
} }
+12
View File
@@ -540,6 +540,7 @@
6298AB0906DDD3525CD78C6B /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; }; 6298AB0906DDD3525CD78C6B /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; };
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; }; 62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; }; 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; };
633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */; };
63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; }; 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; };
6386EA3C898AD1A4BC1DC8A5 /* TimelineMediaPreviewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */; }; 6386EA3C898AD1A4BC1DC8A5 /* TimelineMediaPreviewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */; };
639A0A27383EC655B0E81E95 /* SpaceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459A3921046977CBF4F3C359 /* SpaceScreenViewModelProtocol.swift */; }; 639A0A27383EC655B0E81E95 /* SpaceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459A3921046977CBF4F3C359 /* SpaceScreenViewModelProtocol.swift */; };
@@ -2458,6 +2459,7 @@
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; }; B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; }; B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; };
B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = "<group>"; }; B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = "<group>"; };
B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyTextButton.swift; sourceTree = "<group>"; };
B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = "<group>"; }; B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = "<group>"; };
B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenModels.swift; sourceTree = "<group>"; }; B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenModels.swift; sourceTree = "<group>"; };
B6A293D06BAB2B7A17D9314B /* VoiceMessageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineView.swift; sourceTree = "<group>"; }; B6A293D06BAB2B7A17D9314B /* VoiceMessageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineView.swift; sourceTree = "<group>"; };
@@ -3584,6 +3586,7 @@
07934EF08BB39353E4A94272 /* BlurEffectView.swift */, 07934EF08BB39353E4A94272 /* BlurEffectView.swift */,
8CC23C63849452BC86EA2852 /* ButtonStyle.swift */, 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */,
FEC4B431B0117BDEE697DB4A /* ComposerDisabledView.swift */, FEC4B431B0117BDEE697DB4A /* ComposerDisabledView.swift */,
B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */,
E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */, E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */,
8F4F0AB250EFA7B71FB2BDB2 /* HorizontalHighlightGradient.swift */, 8F4F0AB250EFA7B71FB2BDB2 /* HorizontalHighlightGradient.swift */,
F320003F490B11F808ECC5E9 /* JoinedMembersBadgeView.swift */, F320003F490B11F808ECC5E9 /* JoinedMembersBadgeView.swift */,
@@ -5848,6 +5851,13 @@
path = MapLibre; path = MapLibre;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
C18958141C8ED6D778F779A4 /* CreateRoom */ = {
isa = PBXGroup;
children = (
);
path = CreateRoom;
sourceTree = "<group>";
};
C1CD278862878F9545608040 /* SessionVerificationScreen */ = { C1CD278862878F9545608040 /* SessionVerificationScreen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -6230,6 +6240,7 @@
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */, 53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
1185EECDD07495D65AC84AFC /* CallScreen */, 1185EECDD07495D65AC84AFC /* CallScreen */,
90DC2E28718955ED87AD1456 /* CreatePollScreen */, 90DC2E28718955ED87AD1456 /* CreatePollScreen */,
C18958141C8ED6D778F779A4 /* CreateRoom */,
821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */, 821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */,
3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */, 3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */,
45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */, 45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */,
@@ -7707,6 +7718,7 @@
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */, EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */,
AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */, AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */,
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */, C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */,
633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */,
CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */, CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */,
D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */, D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */,
9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */, 9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */,
@@ -500,9 +500,11 @@
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_action" = "Ban"; "screen_bottom_sheet_manage_room_member_ban_member_confirmation_action" = "Ban";
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_description" = "They won’t be able to join again if invited."; "screen_bottom_sheet_manage_room_member_ban_member_confirmation_description" = "They won’t be able to join again if invited.";
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_title" = "Are you sure you want to ban this member?"; "screen_bottom_sheet_manage_room_member_ban_member_confirmation_title" = "Are you sure you want to ban this member?";
"screen_bottom_sheet_manage_room_member_ban_member_from_space_confirmation_description" = "They won’t be able to join this space again if invited, but they’ll still keep their memberships of any rooms or subspaces.";
"screen_bottom_sheet_manage_room_member_banning_user" = "Banning %1$@"; "screen_bottom_sheet_manage_room_member_banning_user" = "Banning %1$@";
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_action" = "Remove"; "screen_bottom_sheet_manage_room_member_kick_member_confirmation_action" = "Remove";
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_title" = "Are you sure you want to remove this member?"; "screen_bottom_sheet_manage_room_member_kick_member_confirmation_title" = "Are you sure you want to remove this member?";
"screen_bottom_sheet_manage_room_member_kick_member_from_space_confirmation_description" = "They will be able to join this space again if invited, and they’ll still keep their memberships of any rooms or subspaces.";
"screen_bottom_sheet_manage_room_member_member_user_info" = "View profile"; "screen_bottom_sheet_manage_room_member_member_user_info" = "View profile";
"screen_bottom_sheet_manage_room_member_remove" = "Remove user"; "screen_bottom_sheet_manage_room_member_remove" = "Remove user";
"screen_bottom_sheet_manage_room_member_remove_confirmation_title" = "Remove member and ban from joining in the future?"; "screen_bottom_sheet_manage_room_member_remove_confirmation_title" = "Remove member and ban from joining in the future?";
+4
View File
@@ -1322,6 +1322,8 @@ internal enum L10n {
internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_description") } internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_description") }
/// Are you sure you want to ban this member? /// Are you sure you want to ban this member?
internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_title") } internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_title") }
/// They won’t be able to join this space again if invited, but they’ll still keep their memberships of any rooms or subspaces.
internal static var screenBottomSheetManageRoomMemberBanMemberFromSpaceConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_from_space_confirmation_description") }
/// Banning %1$@ /// Banning %1$@
internal static func screenBottomSheetManageRoomMemberBanningUser(_ p1: Any) -> String { internal static func screenBottomSheetManageRoomMemberBanningUser(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_banning_user", String(describing: p1)) return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_banning_user", String(describing: p1))
@@ -1332,6 +1334,8 @@ internal enum L10n {
internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_description") } internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_description") }
/// Are you sure you want to remove this member? /// Are you sure you want to remove this member?
internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_title") } internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_title") }
/// They will be able to join this space again if invited, and they’ll still keep their memberships of any rooms or subspaces.
internal static var screenBottomSheetManageRoomMemberKickMemberFromSpaceConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_from_space_confirmation_description") }
/// View profile /// View profile
internal static var screenBottomSheetManageRoomMemberMemberUserInfo: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_member_user_info") } internal static var screenBottomSheetManageRoomMemberMemberUserInfo: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_member_user_info") }
/// Remove user /// Remove user
@@ -0,0 +1,39 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Compound
import SwiftUI
/// A button that contains text that is copied on tap
struct CopyTextButton: View {
let content: String
var body: some View {
Button {
UIPasteboard.general.string = content
} label: {
Label {
Text(content)
.lineLimit(1)
} icon: {
CompoundIcon(\.copy, size: .small, relativeTo: .compound.bodyLG)
.accessibilityHidden(true)
}
.font(.compound.bodyLG)
.foregroundStyle(.compound.textSecondary)
.labelStyle(.custom(spacing: 4, iconLayout: .trailing))
}
.accessibilityHint(L10n.actionCopy)
}
}
struct CopyTextButton_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
CopyTextButton(content: "Copy me!")
.previewLayout(.sizeThatFits)
}
}
@@ -30,6 +30,7 @@ enum TestablePreviewsDictionary {
"CollapsibleRoomTimelineView_Previews" : CollapsibleRoomTimelineView_Previews.self, "CollapsibleRoomTimelineView_Previews" : CollapsibleRoomTimelineView_Previews.self,
"CompletionSuggestion_Previews" : CompletionSuggestion_Previews.self, "CompletionSuggestion_Previews" : CompletionSuggestion_Previews.self,
"ComposerToolbar_Previews" : ComposerToolbar_Previews.self, "ComposerToolbar_Previews" : ComposerToolbar_Previews.self,
"CopyTextButton_Previews" : CopyTextButton_Previews.self,
"CreateRoom_Previews" : CreateRoom_Previews.self, "CreateRoom_Previews" : CreateRoom_Previews.self,
"DeactivateAccountScreen_Previews" : DeactivateAccountScreen_Previews.self, "DeactivateAccountScreen_Previews" : DeactivateAccountScreen_Previews.self,
"DeclineAndBlockScreen_Previews" : DeclineAndBlockScreen_Previews.self, "DeclineAndBlockScreen_Previews" : DeclineAndBlockScreen_Previews.self,
@@ -59,7 +59,7 @@ class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, Manage
case .kick: case .kick:
state.bindings.alertInfo = .init(id: alertType, state.bindings.alertInfo = .init(id: alertType,
title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationTitle, title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationTitle,
message: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationDescription, message: roomProxy.infoPublisher.value.isSpace ? L10n.screenBottomSheetManageRoomMemberKickMemberFromSpaceConfirmationDescription : L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationDescription,
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { }, primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationAction) { [weak self] in Task { await self?.kickMember(id: memberID, name: memberName, reason: reason) } }, secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationAction) { [weak self] in Task { await self?.kickMember(id: memberID, name: memberName, reason: reason) } },
textFields: [.init(placeholder: L10n.commonReason, textFields: [.init(placeholder: L10n.commonReason,
@@ -69,7 +69,7 @@ class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, Manage
case .ban: case .ban:
state.bindings.alertInfo = .init(id: alertType, state.bindings.alertInfo = .init(id: alertType,
title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationTitle, title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationTitle,
message: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationDescription, message: roomProxy.infoPublisher.value.isSpace ? L10n.screenBottomSheetManageRoomMemberBanMemberFromSpaceConfirmationDescription : L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationDescription,
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { }, primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationAction) { [weak self] in Task { await self?.banMember(id: memberID, name: memberName, reason: reason) } }, secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationAction) { [weak self] in Task { await self?.banMember(id: memberID, name: memberName, reason: reason) } },
textFields: [.init(placeholder: L10n.commonReason, textFields: [.init(placeholder: L10n.commonReason,
@@ -28,6 +28,10 @@ struct SpaceHeaderView: View {
.foregroundStyle(.compound.textPrimary) .foregroundStyle(.compound.textPrimary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
if let alias = spaceRoomProxy.canonicalAlias {
CopyTextButton(content: alias)
}
spaceDetails spaceDetails
JoinedMembersBadgeView(heroes: spaceRoomProxy.heroes, JoinedMembersBadgeView(heroes: spaceRoomProxy.heroes,
@@ -62,7 +66,7 @@ struct SpaceHeaderView: View {
} }
} }
var spaceDetails: some View { private var spaceDetails: some View {
Label { Label {
Text(spaceDetailsVisibilityTitle) Text(spaceDetailsVisibilityTitle)
.font(.compound.bodyLG) .font(.compound.bodyLG)
@@ -74,7 +78,7 @@ struct SpaceHeaderView: View {
} }
} }
var spaceDetailsVisibilityTitle: String { private var spaceDetailsVisibilityTitle: String {
switch spaceRoomProxy.visibility { switch spaceRoomProxy.visibility {
case .public: L10n.commonPublicSpace case .public: L10n.commonPublicSpace
case .private: L10n.commonPrivateSpace case .private: L10n.commonPrivateSpace
@@ -83,7 +87,7 @@ struct SpaceHeaderView: View {
} }
} }
var spaceDetailsVisibilityIcon: KeyPath<CompoundIcons, Image> { private var spaceDetailsVisibilityIcon: KeyPath<CompoundIcons, Image> {
switch spaceRoomProxy.visibility { switch spaceRoomProxy.visibility {
case .public: \.public case .public: \.public
case .private: \.lock case .private: \.lock
@@ -122,6 +126,7 @@ struct SpaceHeaderView_Previews: PreviewProvider, TestablePreview {
childrenCount: 20, childrenCount: 20,
joinedMembersCount: 78, joinedMembersCount: 78,
topic: "Description of the space goes right here.", topic: "Description of the space goes right here.",
canonicalAlias: "#space:matrix.org",
joinRule: .public)), joinRule: .public)),
SpaceRoomProxyMock(.init(id: "!space3:matrix.org", SpaceRoomProxyMock(.init(id: "!space3:matrix.org",
name: "Subspace", name: "Subspace",
@@ -135,6 +140,7 @@ struct SpaceHeaderView_Previews: PreviewProvider, TestablePreview {
"Sem amet enim habitant nibh augue mauris.", "Sem amet enim habitant nibh augue mauris.",
"Interdum mauris ultrices tincidunt proin morbi erat aenean risus nibh.", "Interdum mauris ultrices tincidunt proin morbi erat aenean risus nibh.",
"Diam amet sit fermentum vulputate faucibus."].joined(separator: " "), "Diam amet sit fermentum vulputate faucibus."].joined(separator: " "),
canonicalAlias: "#subspace:matrix.org",
joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")]))) joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")])))
] ]
} }
@@ -28,7 +28,12 @@ struct SpaceScreenViewState: BindableState {
var selectedSpaceRoomID: String? var selectedSpaceRoomID: String?
var joiningRoomIDs: Set<String> = [] var joiningRoomIDs: Set<String> = []
var isSpaceManagementEnabled = false var canEditBaseInfo = false
var canEditRolesAndPermissions = false
var isSpaceManagementEnabled: Bool {
canEditBaseInfo || canEditRolesAndPermissions
}
var bindings = SpaceScreenViewStateBindings() var bindings = SpaceScreenViewStateBindings()
} }
@@ -80,7 +80,17 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
} }
appSettings.$spaceSettingsEnabled appSettings.$spaceSettingsEnabled
.weakAssign(to: \.state.isSpaceManagementEnabled, on: self) .combineLatest(roomProxy.infoPublisher)
.sink { [weak self] isEnabled, roomInfo in
guard let self else { return }
guard isEnabled, let powerLevels = roomInfo.powerLevels else {
state.canEditBaseInfo = false
state.canEditRolesAndPermissions = false
return
}
state.canEditBaseInfo = powerLevels.canOwnUserEditBaseInfo()
state.canEditRolesAndPermissions = powerLevels.canOwnUserEditRolesAndPermissions()
}
.store(in: &cancellables) .store(in: &cancellables)
} }
} }
@@ -97,7 +97,7 @@ struct LeaveSpaceView: View {
Label(leaveHandle.confirmationTitle, icon: \.leave) Label(leaveHandle.confirmationTitle, icon: \.leave)
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
} else if context.viewState.isSpaceManagementEnabled { } else if context.viewState.canEditRolesAndPermissions {
Button { Button {
context.send(viewAction: .rolesAndPermissions) context.send(viewAction: .rolesAndPermissions)
} label: { } label: {
@@ -114,6 +114,7 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
joinedMembersCount: 76, joinedMembersCount: 76,
heroes: [.mockDan, .mockBob, .mockCharlie, .mockVerbose], heroes: [.mockDan, .mockBob, .mockCharlie, .mockVerbose],
topic: "Description of the space goes right here. Lorem ipsum dolor sit amet consectetur. Leo viverra morbi habitant in.", topic: "Description of the space goes right here. Lorem ipsum dolor sit amet consectetur. Leo viverra morbi habitant in.",
canonicalAlias: "#engineering-team:element.io",
joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")]))) joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")])))
let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceRoomProxy: spaceRoomProxy, let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceRoomProxy: spaceRoomProxy,
initialSpaceRooms: .mockSpaceList)) initialSpaceRooms: .mockSpaceList))
@@ -15,6 +15,7 @@ struct SpaceSettingsScreenViewState: BindableState {
var joinedMembersCount: Int var joinedMembersCount: Int
var hasMemberIdentityVerificationStateViolations = false var hasMemberIdentityVerificationStateViolations = false
var canEditBaseInfo = false
var canEditRolesOrPermissions = false var canEditRolesOrPermissions = false
} }
@@ -57,6 +57,7 @@ class SpaceSettingsScreenViewModel: SpaceSettingsScreenViewModelType, SpaceSetti
if let powerLevels = roomInfo.powerLevels { if let powerLevels = roomInfo.powerLevels {
state.canEditRolesOrPermissions = powerLevels.canOwnUserEditRolesAndPermissions() state.canEditRolesOrPermissions = powerLevels.canOwnUserEditRolesAndPermissions()
state.canEditBaseInfo = powerLevels.canOwnUserEditBaseInfo()
} }
} }
@@ -26,17 +26,24 @@ struct SpaceSettingsScreen: View {
private var editSection: some View { private var editSection: some View {
Section { Section {
ListRow(kind: .custom { ListRow(kind: .custom { editRow })
Button {
context.send(viewAction: .processTapEdit)
} label: {
editSectionContent
}
})
} }
} }
private var editSectionContent: some View { @ViewBuilder
private var editRow: some View {
if context.viewState.canEditBaseInfo {
Button {
context.send(viewAction: .processTapEdit)
} label: {
editRowContent
}
} else {
editRowContent
}
}
private var editRowContent: some View {
HStack(spacing: 12) { HStack(spacing: 12) {
RoomAvatarImage(avatar: context.viewState.details.avatar, RoomAvatarImage(avatar: context.viewState.details.avatar,
avatarSize: .room(on: .spaceSettings), avatarSize: .room(on: .spaceSettings),
@@ -57,8 +64,10 @@ struct SpaceSettingsScreen: View {
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
if context.viewState.canEditBaseInfo {
ListRowAccessory.navigationLink ListRowAccessory.navigationLink
} }
}
.padding(.horizontal, ListRowPadding.horizontal) .padding(.horizontal, ListRowPadding.horizontal)
.padding(.vertical, 16) .padding(.vertical, 16)
} }
@@ -108,16 +117,29 @@ struct SpaceSettingsScreen: View {
// MARK: - Previews // MARK: - Previews
struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview { struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = SpaceSettingsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space", static let ownerViewModel = SpaceSettingsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar, avatarURL: .mockMXCAvatar,
isSpace: true, isSpace: true,
canonicalAlias: "#space:matrix.org", canonicalAlias: "#space:matrix.org",
members: .allMembersAsCreator)), members: .allMembersAsCreator)),
userSession: UserSessionMock(.init())) userSession: UserSessionMock(.init()))
static let userViewModel = SpaceSettingsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar,
isSpace: true,
canonicalAlias: "#space:matrix.org",
members: .allMembers)),
userSession: UserSessionMock(.init()))
static var previews: some View { static var previews: some View {
NavigationStack { NavigationStack {
SpaceSettingsScreen(context: viewModel.context) SpaceSettingsScreen(context: ownerViewModel.context)
} }
.previewDisplayName("Owner")
NavigationStack {
SpaceSettingsScreen(context: userViewModel.context)
}
.previewDisplayName("User")
} }
} }
@@ -36,3 +36,12 @@ protocol RoomPowerLevelsProxyProtocol {
func canUserPinOrUnpin(userID: String) -> Result<Bool, RoomProxyError> func canUserPinOrUnpin(userID: String) -> Result<Bool, RoomProxyError>
func canUserJoinCall(userID: String) -> Result<Bool, RoomProxyError> func canUserJoinCall(userID: String) -> Result<Bool, RoomProxyError>
} }
// MARK: - Helpers
extension RoomPowerLevelsProxyProtocol {
/// Can own user edit either the room name, avatar or topic
func canOwnUserEditBaseInfo() -> Bool {
canOwnUser(sendStateEvent: .roomAvatar) || canOwnUser(sendStateEvent: .roomName) || canOwnUser(sendStateEvent: .roomTopic)
}
}
@@ -137,6 +137,12 @@ extension PreviewTests {
} }
} }
func testCopyTextButton() async throws {
for (index, preview) in CopyTextButton_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
}
}
func testCreateRoom() async throws { func testCreateRoom() async throws {
for (index, preview) in CreateRoom_Previews._allPreviews.enumerated() { for (index, preview) in CreateRoom_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index) try await assertSnapshots(matching: preview, step: index)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3d494a30652c54d4ab3d503eecfb1b849db78f7ea372d392f5b93efc2a3ecd7
size 5941
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3d494a30652c54d4ab3d503eecfb1b849db78f7ea372d392f5b93efc2a3ecd7
size 5941
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:824417be28d83b6cf5a3200e3e792e639c648b3a6288bc29fe461b85b069a7ae
size 4544
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:824417be28d83b6cf5a3200e3e792e639c648b3a6288bc29fe461b85b069a7ae
size 4544
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:41a11b24dc33a848b1e92e221ebf6f7eed9da0d17883baba30b49488a8dd916d oid sha256:486d97c665bfd6f9ac41c3c65182476080467159d4dda64e89e725960ac8a3f7
size 203835 size 216418
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:80abb6f07c275acaedb46078abfac094b304d3c799c050003e1d20dca3d745fa oid sha256:dfc40f590b6c37545bfa46c7569ee27b6a0ae868a55960a53e2fd3d74c564412
size 206909 size 219887
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f972c103ce2c14ed5c676741f9a3f8f480702815e677749d46908abd0fa7dada oid sha256:5778379ebc5f0df879016579fa4dccfce7e9546dc06fd5b9cb5fa3cfd3e8f050
size 132742 size 143496
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:430dfde8c142f7ad7474be587c0c217daa9cb9801fe0ff0eff291f8f9cbf57cf oid sha256:5adc8046dfeadc62fb0d22fba84e0f5c0c61c7bb780a432b800e2cdb29066427
size 139505 size 149951
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f0657e0d482b9a3bc949d0bd9b2206baa14413c58031a9333cdd105ccecab4d8 oid sha256:96f0a0f4dd66edce3a75e51a16d059bdfbe32ef61dc8e78ccbb22868205c36ea
size 234610 size 233953
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2f770bab4c29db4148a4266a9d2956f4b6268ec473b8a013a5aa89219613f5db oid sha256:c4c8eb821de73289ae5c220b9c1fa9f2e58c33e3617634ae0cacb5d9043e587f
size 264406 size 263913
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5b2acf1f719ab9acb6887d700607a3e741ec69ae7cabbfd3fd2d00ebef7c7ced oid sha256:8891be05350788529385e39d9edf71e52c7d2536a471773b831358278b316973
size 175169 size 175952
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f68408d2eca92a7dec827ef79f01179ae58c4a5b55dd4d2c6de425d525d06842 oid sha256:a46b3ab9af6aeca9bc98ffd6efe3669a03361c8efc6cba99e303d04f11fdbc07
size 200045 size 197703
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e172a847ec495abaf46e8a42879252a75db06fa3e2cd4ff1c31964726f08422e
size 125408
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d2133099664d02d4c53a86ad516f1e6c321e852455504ac18acd135267d109f6
size 129691
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e2b5de1805e2d163be5b82ef98814cedacef5c02ee18160f55856a8fd7bd1b9
size 78430
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e83067633005469ed20f25d0d6e1c92d436983c230860faed77b65bebb3c0bc0
size 82233