Add history sharing badges to room details (#5043)

* chore: Add dependency `SwiftFlow` for use in avatar header badges.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>

* feat: Display history sharing pill in room details.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>

* tests: Add tests for history sharing room details pill.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>

* tests: Update screenshots for RoomDetailsScreen preview tests.

* fix: Use Localazy translations for room details badges

* fix: Address review comments

* fix: Remove line limit from room details badges

---------

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Skye Elliot
2026-02-11 12:22:39 +00:00
committed by GitHub
parent a81b59778c
commit 293f99d5bc
57 changed files with 333 additions and 67 deletions

View File

@@ -986,7 +986,7 @@
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
A87DC550659C5176AC1829DE /* ElementTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7673F2B0B038FAB2A8D16AD /* ElementTextFieldStyle.swift */; }; A87DC550659C5176AC1829DE /* ElementTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7673F2B0B038FAB2A8D16AD /* ElementTextFieldStyle.swift */; };
A88328D7E17F73AB64501B51 /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; A88328D7E17F73AB64501B51 /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 4D7E6BFC89715FC3CF0349D0 /* Flow */; };
A8E324E700E596E36B0A311B /* BootDetectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F054DE7D47849687662C9D9 /* BootDetectionManager.swift */; }; A8E324E700E596E36B0A311B /* BootDetectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F054DE7D47849687662C9D9 /* BootDetectionManager.swift */; };
A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; };
A91D125414C3D9ABBABCF2F1 /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 6690850AA47ECED7E1CAB345 /* KZFileWatchers */; }; A91D125414C3D9ABBABCF2F1 /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 6690850AA47ECED7E1CAB345 /* KZFileWatchers */; };
@@ -1308,6 +1308,7 @@
E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */; }; E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */; };
E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */; }; E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */; };
E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; }; E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; };
E673A5442A9A912DE6CE097A /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; };
E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; }; E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; };
E6A2F6E4795C1054025AACFF /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AB32690C6CE5C62A86D6FA /* NotificationItemProxy.swift */; }; E6A2F6E4795C1054025AACFF /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AB32690C6CE5C62A86D6FA /* NotificationItemProxy.swift */; };
E6FA87F773424B27614B23E9 /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA7E93C2E148B96EF6A8500 /* TimelineItemAccessibilityModifier.swift */; }; E6FA87F773424B27614B23E9 /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA7E93C2E148B96EF6A8500 /* TimelineItemAccessibilityModifier.swift */; };
@@ -3055,7 +3056,8 @@
A93661C962B12942C08864B6 /* WysiwygComposer in Frameworks */, A93661C962B12942C08864B6 /* WysiwygComposer in Frameworks */,
37E47F5101C0C036289D3807 /* SwiftOGG in Frameworks */, 37E47F5101C0C036289D3807 /* SwiftOGG in Frameworks */,
4610C57A4785FFF5E67F0C6D /* SwiftSoup in Frameworks */, 4610C57A4785FFF5E67F0C6D /* SwiftSoup in Frameworks */,
A88328D7E17F73AB64501B51 /* DSWaveformImageViews in Frameworks */, A88328D7E17F73AB64501B51 /* Flow in Frameworks */,
E673A5442A9A912DE6CE097A /* DSWaveformImageViews in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -7055,6 +7057,7 @@
CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */, CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */,
391D11F92DFC91666AA1503F /* SwiftOGG */, 391D11F92DFC91666AA1503F /* SwiftOGG */,
EDFB92E97D9AC4BA8540C18C /* SwiftSoup */, EDFB92E97D9AC4BA8540C18C /* SwiftSoup */,
4D7E6BFC89715FC3CF0349D0 /* Flow */,
2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */, 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */,
); );
productName = ElementX; productName = ElementX;
@@ -7230,6 +7233,7 @@
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */, 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */,
A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
EBD512E01D6AED903C15C5F5 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */,
E2F3DA35D462724CCC61DE2C /* XCRemoteSwiftPackageReference "swift-ogg" */, E2F3DA35D462724CCC61DE2C /* XCRemoteSwiftPackageReference "swift-ogg" */,
AB8E808A59756170682BEC20 /* XCRemoteSwiftPackageReference "SwiftSoup" */, AB8E808A59756170682BEC20 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */, 6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */,
@@ -9958,6 +9962,14 @@
minimumVersion = 1.18.7; minimumVersion = 1.18.7;
}; };
}; };
EBD512E01D6AED903C15C5F5 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/tevelee/SwiftUI-Flow.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 3.1.1;
};
};
EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */ = { EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mxcl/Version"; repositoryURL = "https://github.com/mxcl/Version";
@@ -10103,6 +10115,11 @@
package = 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */; package = 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */;
productName = PostHog; productName = PostHog;
}; };
4D7E6BFC89715FC3CF0349D0 /* Flow */ = {
isa = XCSwiftPackageProductDependency;
package = EBD512E01D6AED903C15C5F5 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */;
productName = Flow;
};
50009897F60FAE7D63EF5E5B /* Kingfisher */ = { 50009897F60FAE7D63EF5E5B /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */; package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;

View File

@@ -324,6 +324,15 @@
"version" : "6.0.1" "version" : "6.0.1"
} }
}, },
{
"identity" : "swiftui-flow",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tevelee/SwiftUI-Flow.git",
"state" : {
"revision" : "d227f999b2894ab737ef5786d9b14d02d3e5362e",
"version" : "3.1.1"
}
},
{ {
"identity" : "swiftui-introspect", "identity" : "swiftui-introspect",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -7,6 +7,7 @@
// //
import Compound import Compound
import Flow
import SwiftUI import SwiftUI
struct AvatarHeaderView<Footer: View>: View { struct AvatarHeaderView<Footer: View>: View {
@@ -17,6 +18,7 @@ struct AvatarHeaderView<Footer: View>: View {
private enum Badge: Hashable { private enum Badge: Hashable {
case encrypted(Bool) case encrypted(Bool)
case historySharingState(RoomHistorySharingState)
case `public` case `public`
case verified case verified
} }
@@ -57,6 +59,9 @@ struct AvatarHeaderView<Footer: View>: View {
if room.isPublic { if room.isPublic {
badges.append(.public) badges.append(.public)
} }
if let state = room.historySharingState {
badges.append(.historySharingState(state))
}
self.badges = badges self.badges = badges
} }
@@ -129,7 +134,10 @@ struct AvatarHeaderView<Footer: View>: View {
} }
private var badgesStack: some View { private var badgesStack: some View {
HStack(spacing: 8) { HFlow(horizontalAlignment: .center,
verticalAlignment: .top,
horizontalSpacing: 8.0,
verticalSpacing: 8.0) {
ForEach(badges, id: \.self) { badge in ForEach(badges, id: \.self) { badge in
switch badge { switch badge {
case .encrypted(true): case .encrypted(true):
@@ -148,6 +156,18 @@ struct AvatarHeaderView<Footer: View>: View {
BadgeLabel(title: L10n.commonVerified, BadgeLabel(title: L10n.commonVerified,
icon: \.verified, icon: \.verified,
style: .accent) style: .accent)
case .historySharingState(.hidden):
BadgeLabel(title: L10n.cryptoHistorySharingRoomInfoHiddenBadgeContent,
icon: \.visibilityOff,
style: .info)
case .historySharingState(.shared):
BadgeLabel(title: L10n.cryptoHistorySharingRoomInfoSharedBadgeContent,
icon: \.history,
style: .info)
case .historySharingState(.worldReadable):
BadgeLabel(title: L10n.cryptoHistorySharingRoomInfoWorldReadableBadgeContent,
icon: \.userProfileSolid,
style: .info)
} }
} }
} }
@@ -233,7 +253,8 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
canonicalAlias: "#test:matrix.org", canonicalAlias: "#test:matrix.org",
isEncrypted: true, isEncrypted: true,
isPublic: true, isPublic: true,
isDirect: false), isDirect: false,
historySharingState: nil),
avatarSize: .room(on: .details), avatarSize: .room(on: .details),
mediaProvider: MediaProviderMock(configuration: .init())) { mediaProvider: MediaProviderMock(configuration: .init())) {
HStack(spacing: 32) { HStack(spacing: 32) {
@@ -279,5 +300,34 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
.background(Color.compound.bgSubtleSecondaryLevel0) .background(Color.compound.bgSubtleSecondaryLevel0)
.previewLayout(.sizeThatFits) .previewLayout(.sizeThatFits)
.previewDisplayName("Members") .previewDisplayName("Members")
makeHistorySharingPreview(state: .hidden).previewDisplayName("History Sharing - Hidden")
makeHistorySharingPreview(state: .shared).previewDisplayName("History Sharing - Shared")
makeHistorySharingPreview(state: .worldReadable).previewDisplayName("History Sharing - World Readable")
}
private static func makeHistorySharingPreview(state: RoomHistorySharingState) -> some View {
Form {
AvatarHeaderView(room: .init(id: "@test:matrix.org",
name: "Test Room",
avatar: .room(id: "@test:matrix.org",
name: "Test Room",
avatarURL: .mockMXCAvatar),
canonicalAlias: "#test:matrix.org",
isEncrypted: true,
isPublic: true,
isDirect: false,
historySharingState: state),
avatarSize: .room(on: .details),
mediaProvider: MediaProviderMock(configuration: .init())) {
HStack(spacing: 32) {
ShareLink(item: "test") {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: "Test"))
}
.padding(.top, 32)
}
}
} }
} }

View File

@@ -83,6 +83,9 @@ struct BadgeLabel_Previews: PreviewProvider, TestablePreview {
BadgeLabel(title: "1234", BadgeLabel(title: "1234",
icon: \.userProfile, icon: \.userProfile,
style: .default) style: .default)
BadgeLabel(title: "Very long text that potentially will wrap around in constrained environments, maybe into two or three lines, depending on the exact length of the text",
icon: \.userProfile,
style: .default)
} }
} }
} }

View File

@@ -82,9 +82,9 @@ struct RoomHeaderView: View {
private var historySharingIcon: KeyPath<CompoundIcons, Image>? { private var historySharingIcon: KeyPath<CompoundIcons, Image>? {
switch roomHistorySharingState { switch roomHistorySharingState {
case .none, .hidden: nil
case .shared: \.history case .shared: \.history
case .worldReadable: \.userProfileSolid case .worldReadable: \.userProfileSolid
case .none: nil
} }
} }
} }

View File

@@ -61,7 +61,13 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
let topic = attributedStringBuilder.fromPlain(roomProxy.infoPublisher.value.topic) let topic = attributedStringBuilder.fromPlain(roomProxy.infoPublisher.value.topic)
super.init(initialViewState: .init(details: roomProxy.details, // Clear details.historySharingState manually while we are still behind a feature flag.
var details = roomProxy.details
if !appSettings.enableKeyShareOnInvite {
details.historySharingState = nil
}
super.init(initialViewState: .init(details: details,
isEncrypted: roomProxy.infoPublisher.value.isEncrypted, isEncrypted: roomProxy.infoPublisher.value.isEncrypted,
isDirect: roomProxy.infoPublisher.value.isDirect, isDirect: roomProxy.infoPublisher.value.isDirect,
topic: topic, topic: topic,
@@ -275,6 +281,14 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
state.details = roomProxy.details state.details = roomProxy.details
// Set state.details.historySharingState manually while we are still behind
// a feature flag.
if appSettings.enableKeyShareOnInvite {
state.details.historySharingState = roomInfo.historySharingState
} else {
state.details.historySharingState = nil
}
let topic = attributedStringBuilder.fromPlain(roomInfo.topic) let topic = attributedStringBuilder.fromPlain(roomInfo.topic)
state.topic = topic state.topic = topic
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces() state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()

View File

@@ -328,17 +328,24 @@ struct RoomDetailsScreen: View {
// MARK: - Previews // MARK: - Previews
import MatrixRustSDK
struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
static let genericRoomViewModel = makeGenericRoomViewModel() static let genericWorldReadableRoomViewModel = makeGenericRoomViewModel(historyVisibility: .worldReadable)
static let genericJoinedRoomViewModel = makeGenericRoomViewModel(historyVisibility: .joined)
static let simpleRoomViewModel = makeSimpleRoomViewModel() static let simpleRoomViewModel = makeSimpleRoomViewModel()
static let dmRoomViewModel = makeDMViewModel(verificationState: .notVerified) static let dmRoomViewModel = makeDMViewModel(verificationState: .notVerified)
static let dmRoomVerifiedViewModel = makeDMViewModel(verificationState: .verified) static let dmRoomVerifiedViewModel = makeDMViewModel(verificationState: .verified)
static let dmRoomVerificationViolationViewModel = makeDMViewModel(verificationState: .verificationViolation) static let dmRoomVerificationViolationViewModel = makeDMViewModel(verificationState: .verificationViolation)
static var previews: some View { static var previews: some View {
RoomDetailsScreen(context: genericRoomViewModel.context) RoomDetailsScreen(context: genericJoinedRoomViewModel.context)
.snapshotPreferences(expect: genericRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil }) .snapshotPreferences(expect: genericJoinedRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil })
.previewDisplayName("Generic Room") .previewDisplayName("Generic Room - Joined History Visibility")
RoomDetailsScreen(context: genericWorldReadableRoomViewModel.context)
.snapshotPreferences(expect: genericWorldReadableRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil })
.previewDisplayName("Generic Room - World Readable History Visibility")
RoomDetailsScreen(context: simpleRoomViewModel.context) RoomDetailsScreen(context: simpleRoomViewModel.context)
.snapshotPreferences(expect: simpleRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil }) .snapshotPreferences(expect: simpleRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil })
@@ -357,7 +364,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("DM Room Verification Violation") .previewDisplayName("DM Room Verification Violation")
} }
private static func makeGenericRoomViewModel() -> RoomDetailsScreenViewModel { private static func makeGenericRoomViewModel(historyVisibility: RoomHistoryVisibility) -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
let knockRequests: [KnockRequestProxyMock] = [.init()] let knockRequests: [KnockRequestProxyMock] = [.init()]
@@ -381,6 +389,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
isDirect: false, isDirect: false,
isEncrypted: true, isEncrypted: true,
canonicalAlias: "#alias:domain.com", canonicalAlias: "#alias:domain.com",
historyVisibility: historyVisibility,
members: members, members: members,
knockRequestsState: .loaded(knockRequests), knockRequestsState: .loaded(knockRequests),
joinRule: .knock)) joinRule: .knock))
@@ -400,6 +409,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
} }
private static func makeSimpleRoomViewModel() -> RoomDetailsScreenViewModel { private static func makeSimpleRoomViewModel() -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
let knockRequests: [KnockRequestProxyMock] = [.init()] let knockRequests: [KnockRequestProxyMock] = [.init()]
@@ -429,6 +439,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
} }
private static func makeDMViewModel(verificationState: UserIdentityVerificationState) -> RoomDetailsScreenViewModel { private static func makeDMViewModel(verificationState: UserIdentityVerificationState) -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
let members: [RoomMemberProxyMock] = [ let members: [RoomMemberProxyMock] = [
.mockMe, .mockMe,
.mockDan .mockDan

View File

@@ -16,4 +16,5 @@ struct RoomDetails {
let isEncrypted: Bool let isEncrypted: Bool
let isPublic: Bool let isPublic: Bool
let isDirect: Bool let isDirect: Bool
var historySharingState: RoomHistorySharingState?
} }

View File

@@ -8,8 +8,13 @@
/// Enumeration of the two possible cases in which history sharing under MSC4268 is enabled. These /// Enumeration of the two possible cases in which history sharing under MSC4268 is enabled. These
/// variants implicitly assume that the feature flag, `enableKeyShareOnInvite`, is set. /// variants implicitly assume that the feature flag, `enableKeyShareOnInvite`, is set.
enum RoomHistorySharingState: Equatable { enum RoomHistorySharingState: Equatable {
/// The feature flag is set, and the room history visibility is set to `shared`. /// The feature flag is set, and the room history visibility is either `invited` or `joined`. New
/// members of the room cannot read the room history.
case hidden
/// The feature flag is set, and the room history visibility is set to `shared`. New members of the
/// room can read the room history.
case shared case shared
/// The feature flag is set, and the room history visibility is set to `world_readable`. /// The feature flag is set, and the room history visibility is set to `world_readable`. Anyone
/// can read the room history.
case worldReadable case worldReadable
} }

View File

@@ -142,11 +142,13 @@ extension RoomInfoProxyProtocol {
return nil return nil
} }
return switch historyVisibility { return switch historyVisibility {
case .joined, .invited:
.hidden
case .shared: case .shared:
.shared .shared
case .worldReadable: case .worldReadable:
.worldReadable .worldReadable
default: case .custom:
nil nil
} }
} }

View File

@@ -196,13 +196,20 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
extension JoinedRoomProxyProtocol { extension JoinedRoomProxyProtocol {
var details: RoomDetails { var details: RoomDetails {
RoomDetails(id: id, let historySharingState: RoomHistorySharingState? = if infoPublisher.value.isEncrypted {
infoPublisher.value.historySharingState
} else {
nil
}
return RoomDetails(id: id,
name: infoPublisher.value.displayName, name: infoPublisher.value.displayName,
avatar: infoPublisher.value.avatar, avatar: infoPublisher.value.avatar,
canonicalAlias: infoPublisher.value.canonicalAlias, canonicalAlias: infoPublisher.value.canonicalAlias,
isEncrypted: infoPublisher.value.isEncrypted, isEncrypted: infoPublisher.value.isEncrypted,
isPublic: !(infoPublisher.value.isPrivate ?? false), isPublic: !(infoPublisher.value.isPrivate ?? false),
isDirect: infoPublisher.value.isDirect) isDirect: infoPublisher.value.isDirect,
historySharingState: historySharingState)
} }
var isDirectOneToOneRoom: Bool { var isDirectOneToOneRoom: Bool {

View File

@@ -266,6 +266,14 @@
<key>Type</key> <key>Type</key>
<string>PSChildPaneSpecifier</string> <string>PSChildPaneSpecifier</string>
</dict> </dict>
<dict>
<key>File</key>
<string>Packages/Flow</string>
<key>Title</key>
<string>Flow</string>
<key>Type</key>
<string>PSChildPaneSpecifier</string>
</dict>
<dict> <dict>
<key>File</key> <key>File</key>
<string>Packages/swiftui-introspect</string> <string>Packages/swiftui-introspect</string>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>FooterText</key>
<string>MIT License
Copyright (c) 2023 Laszlo Teveli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -249,6 +249,8 @@ targets:
- package: WysiwygComposer - package: WysiwygComposer
- package: SwiftOGG - package: SwiftOGG
- package: SwiftSoup - package: SwiftSoup
- package: SwiftFlow
product: Flow
- package: DSWaveformImage - package: DSWaveformImage
product: DSWaveformImageViews product: DSWaveformImageViews

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2167dd7ad4982f5eb9c2c2683f15248d6b09452ab14a2ba96c82514a52da68ff
size 179848

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d670ed296bde027d89172fc7bd4e8c346ce74a567a5f43d6cdc1c85771aac26
size 183341

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f9748468c6b8526cb3edb74f9e7d29f01d9d76206af895df7ddeac01447795e6
size 120386

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0a84c0c0f05588b6fe9d168911ea288965ad1ada91b0606a27aa87e8417edabf
size 129238

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d544617c5a81860c4fecd35627b0e8045c8c154279aa464841e5d2325d0aea8
size 179080

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d95e87775dac08b0e1e17da832ad9ad6c3e9c4e4fba927c6fc8f84fbcc48c0a
size 181875

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a5df7ba084647f1427f7f1b9cfc32133d0a5a643fd1532f0f2bc6c1b919a7c92
size 119450

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f045d8db42356b17f8923b4c8777c9263a685760cc590e320b6955900e58ce80
size 124049

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c1bed0883e5158e4969ad6d23ab0fce4c9e1d9558c579cbb47e3bfd06d5406b
size 179125

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e237131c8bebf846c5479a35429c4fe0bab3d3887685d104e1f91aa5c6a5a01e
size 181590

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bbafe0c3eb4a3b69a9714595ee1808c98307fe0180db936cc087fb2b866a1811
size 119208

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6193b1258a154886f8a1963bb163b87bbd81b92c52f754d7cea0cc419899ec7b
size 125046

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:73fd7a0c05e913a7866d130cbd1a8885ca9aa7f9b4a302635c889f75b6be347e oid sha256:a0636504b269de8c13026fe2f7400059f762e92ab7cf2781fd951fe90f37dad7
size 118473 size 116509

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6f251951b19c194c99b13a5cc258a7a3346dfc69ff054eb2d6788d7faff823ac oid sha256:2d442ec46d6e37256ef69826f71040682b91feb7d6c00c95936b62df52eed10b
size 79171 size 97026

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6f251951b19c194c99b13a5cc258a7a3346dfc69ff054eb2d6788d7faff823ac oid sha256:2d442ec46d6e37256ef69826f71040682b91feb7d6c00c95936b62df52eed10b
size 79171 size 97026

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:52933a15fe6e8e66fb981f67f5b10622b6010e987f68e28c0db0fa077736b2b8 oid sha256:8017704510d0461df8592b5e321ae5c4917ec8a9c225f917de1cdc8892ea904b
size 37557 size 58270

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:52933a15fe6e8e66fb981f67f5b10622b6010e987f68e28c0db0fa077736b2b8 oid sha256:8017704510d0461df8592b5e321ae5c4917ec8a9c225f917de1cdc8892ea904b
size 37557 size 58270

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c098195e4c606b478aaa8f5af1b4fdf5c5e2e083a81ef3724e9b4dc5fa146df oid sha256:29d0a32b20eae97fc66255571f1f57aff1652856be4c389dfd54141e10d65a06
size 233433 size 240693

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1922463c444132d5edf8f1e41e5c0ad4a264b3b40ce62692087d28f4d8618545 oid sha256:0c392ac244452b7386e999c49018916e3a5d9b15a50551ec7df0eb7e4eb9c969
size 240156 size 249176

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4a7a42e80df9e4cd631e46a854bb1a96575e78aad363ad4864dd9030fc9c981e oid sha256:ba3d176cd40dec64a7ff93370b6398c456d69ad29784536496f3da53d73c1295
size 172891 size 178046

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:567838cb833342100163a259127741b13eb71041722aec6701fb8e9517d84f4e oid sha256:4ab40f1383d88bf179eb43ef8ca01f4320ed16772a107ad9d50c5113a33041ba
size 183466 size 185438

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c64ba707d33ec802c54f057605cf8fca4b153d64b3b0db57ec1899d07230d6a7 oid sha256:04024e9b258e69e7d67a3b767a302985f40df5aa863d612723bf5ae6c2f14dd4
size 233536 size 240796

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4dd9314a5e1de6983b2091b07ff790d4b96e8cebf3b4206f10810a6b8f001e68 oid sha256:a258f22324985c06782ad04cdf29a78b721e781cb900981ca8ef6c2a53580011
size 240258 size 249278

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d0a702505151b92a5b3313bf68e5b6c8402b0cde0abf4733b5fbec178b4711b2 oid sha256:70e20343f87ad321ff4d2977ae328fbffbe304a35691623284447ad7649cc681
size 172987 size 178142

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:63800beda0a3376f13d0032e31b513f584ea0f51a19d83a256de16ebc42e6eea oid sha256:5a19bbf33e04082040e869a5931c84f8456daed5a79d356ca385c44e8fff30e5
size 183583 size 185557

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8ad685a556ac65dfe738227ecf084d7d7ae3991494fe475662422791e678976c oid sha256:42d00d90c9a922f8e0b97174fcf029d9b343d80e111e445b7703fa6c1891eb48
size 232601 size 239861

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6efa808fd82b7d2c3bc3200e510b042c784a460ed1e74666173f6d0dc697e76d oid sha256:06ba15d8fb7fd8c64bc076bab86a628e57eba42eed356c0285e7e04ed75ada75
size 239307 size 248327

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:73939ae1d34c36a79ab9374b7be5079db18ee243fe35ea926a89018635f62bd2 oid sha256:a228f6fff2856ea786ff194db2cc8325b017a3454eb4991ee6f41803edb82bd7
size 172157 size 177312

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:32b1ede34c4627da18c542ebc517484c5db26ea9d4f87651d0247f03601b5020 oid sha256:16a360b9ec7a856293c5b8f98bee5f21a1d72958673c753b3df8545413b29fdd
size 182745 size 184678

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5c2a248f1c38cd397c5e4452cad23757e87173dff152138fee4b028c43d368c
size 176608

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71e6f9b94e4d491e564ed46542d959a004fe70d3930538c875d510a2d5d08cb4
size 187352

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:348ed4a21be36fc98229b3c55cc8e2ee31669cfae27a742100475ee82455a7c9
size 113350

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:175845727c416af44ccdc6e6eb0980e7d5e98f9433ebb9e3aa4f9d938be053e9
size 126256

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:80b97a205c5d7b0ff2199c06220ced85adb43a25984ca24e543a95375d657d8f
size 175546

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9ab4a838fd589c1eca6e8ae9a970b4021f000b65227de5c5102d813d54aca48
size 185190

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:feca200dd935d6d016cb048e5519a557eba32f273c7e04fef1815dce9566469a
size 111961

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee33a01ef56dde3d7510c411d26470330d3ab20fe994609c897b5c2118c3e780
size 122084

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:759692e980014472271933a9052c0c62b7363ab07b2f435a8f1b77c3a2a2941c
size 169135

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3a1abec99495606348e67383fb690ecb71bca9ef93d99e8ca6d74b2c8918100
size 176844

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f3a0002a9f5b452194640ab8e4e9e3b1d9f0be00e8ae7fcc0ebe5d50453274b2
size 107366

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da65767e55b68320a9797110f9599b9acfcd9dd564bc5a7e47456307ad9be059
size 119828

View File

@@ -746,4 +746,53 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
try await deferred.fulfill() try await deferred.fulfill()
} }
// MARK: - History Sharing
func testHistorySharingPillDoesNotAppearIfFeatureFlagNotSet() async throws {
ServiceLocator.shared.settings.enableKeyShareOnInvite = false
let configuration = JoinedRoomProxyMockConfiguration(historyVisibility: .shared)
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
let roomProxyMock = JoinedRoomProxyMock(configuration)
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: ServiceLocator.shared.settings)
let deferredInvisible = deferFailure(context.observe(\.viewState),
timeout: 1,
message: "The pill should not be shown as the feature flag is not set") { state in
state.details.historySharingState != nil
}
try await deferredInvisible.fulfill()
}
func testHistorySharingPillDisplayedIfHistoryVisibilityShared() async throws {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
let configuration = JoinedRoomProxyMockConfiguration(historyVisibility: .shared)
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
let roomProxyMock = JoinedRoomProxyMock(configuration)
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
userSession: UserSessionMock(.init()),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxyMock,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: ServiceLocator.shared.settings)
let deferredShared = deferFulfillment(context.observe(\.viewState),
message: "The pill should be shown for rooms with shared history visibility") { state in
state.details.historySharingState == .shared
}
try await deferredShared.fulfill()
}
} }

View File

@@ -149,6 +149,9 @@ packages:
SnapshotTesting: SnapshotTesting:
url: https://github.com/pointfreeco/swift-snapshot-testing url: https://github.com/pointfreeco/swift-snapshot-testing
minorVersion: 1.18.7 minorVersion: 1.18.7
SwiftFlow:
url: https://github.com/tevelee/SwiftUI-Flow.git
minorVersion: 3.1.1
SwiftSoup: SwiftSoup:
url: https://github.com/scinfu/SwiftSoup.git url: https://github.com/scinfu/SwiftSoup.git
minorVersion: 2.11.2 minorVersion: 2.11.2