Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ElementX/Sources/Application/Settings/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final class AppSettings {
case seenInvites
case hasSeenSpacesAnnouncement
case hasSeenNewSoundBanner
case acknowledgedHistoryVisibleRooms
case appLockNumberOfPINAttempts
case appLockNumberOfBiometricAttempts
case timelineStyle
Expand Down Expand Up @@ -125,6 +126,7 @@ final class AppSettings {
deviceVerificationURL: URL,
chatBackupDetailsURL: URL,
identityPinningViolationDetailsURL: URL,
historyVisibleDetailsURL: URL,
elementWebHosts: [String],
accountProvisioningHost: String,
bugReportApplicationID: String,
Expand All @@ -144,6 +146,7 @@ final class AppSettings {
self.deviceVerificationURL = deviceVerificationURL
self.chatBackupDetailsURL = chatBackupDetailsURL
self.identityPinningViolationDetailsURL = identityPinningViolationDetailsURL
self.historyVisibleDetailsURL = historyVisibleDetailsURL
self.elementWebHosts = elementWebHosts
self.accountProvisioningHost = accountProvisioningHost
self.bugReportApplicationID = bugReportApplicationID
Expand Down Expand Up @@ -171,6 +174,10 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.hasSeenNewSoundBanner, defaultValue: true, storageType: .userDefaults(store))
var hasSeenNewSoundBanner

/// The Set of room identifiers that the user has acknowledged have visible history.
@UserPreference(key: UserDefaultsKeys.acknowledgedHistoryVisibleRooms, defaultValue: [], storageType: .userDefaults(store))
var acknowledgedHistoryVisibleRooms: Set<String>

/// The initial set of account providers shown to the user in the authentication flow.
///
/// Account provider is the friendly term for the server name. It should not contain an `https` prefix and should
Expand Down Expand Up @@ -202,6 +209,8 @@ final class AppSettings {
private(set) var chatBackupDetailsURL: URL = "https://element.io/help#encryption5"
/// A URL where users can go read more about identity pinning violations
private(set) var identityPinningViolationDetailsURL: URL = "https://element.io/help#encryption18"
/// A URL where users can go to read more about room history sharing.
private(set) var historyVisibleDetailsURL: URL = "https://element.io/en/help#e2ee-history-sharing"
/// Any domains that Element web may be hosted on - used for handling links.
private(set) var elementWebHosts = ["app.element.io", "staging.element.io", "develop.element.io"]
/// The domain that account provisioning links will be hosted on - used for handling the links.
Expand Down
2 changes: 2 additions & 0 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ struct RoomScreenViewStateBindings {
enum RoomScreenFooterViewAction {
case resolvePinViolation(userID: String)
case resolveVerificationViolation(userID: String)
case dismissHistoryVisibleAlert
}

enum RoomScreenFooterViewDetails {
case pinViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL)
case verificationViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL)
case historyVisible(learnMoreURL: URL)
}

enum PinnedEventsBannerState: Equatable {
Expand Down
15 changes: 15 additions & 0 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
Task { await resolveIdentityPinningViolation(userID) }
case .resolveVerificationViolation(let userID):
Task { await resolveIdentityVerificationViolation(userID) }
case .dismissHistoryVisibleAlert:
appSettings.acknowledgedHistoryVisibleRooms.insert(roomProxy.id)
state.footerDetails = nil
}
case .acceptKnock(let eventID):
Task { await acceptKnock(eventID: eventID) }
Expand Down Expand Up @@ -342,6 +345,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.canDeclineKnocks = powerLevels.canOwnUserKick()
state.canBan = powerLevels.canOwnUserBan()
}

if appSettings.enableKeyShareOnInvite {
// Whever the user opens a room with joined history visibility, we clear the dismiss flag to ensure that the banner is displayed again if the history is made visible in the future.
if roomInfo.historyVisibility == RoomHistoryVisibility.joined {
appSettings.acknowledgedHistoryVisibleRooms.remove(roomInfo.id)
state.footerDetails = nil
}
// Whenever the user opens an encrypted room with non-join history visbility, we show them a warning banner if they have not already dismissed it.
else if appSettings.enableKeyShareOnInvite, roomInfo.isEncrypted, !appSettings.acknowledgedHistoryVisibleRooms.contains(roomInfo.id) {
state.footerDetails = .historyVisible(learnMoreURL: appSettings.historyVisibleDetailsURL)
}
}
}

private func setupPinnedEventsTimelineItemProviderIfNeeded() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct RoomScreenFooterView: View {

private var borderColor: Color {
switch details {
case .pinViolation:
case .pinViolation, .historyVisible:
.compound.borderInfoSubtle
case .verificationViolation:
.compound.borderCriticalSubtle
Expand All @@ -27,7 +27,7 @@ struct RoomScreenFooterView: View {

private var gradient: Gradient {
switch details {
case .pinViolation:
case .pinViolation, .historyVisible:
.compound.info
case .verificationViolation:
Gradient(colors: [.compound.bgCriticalSubtle, .clear])
Expand All @@ -54,6 +54,8 @@ struct RoomScreenFooterView: View {
pinViolation(member: member, learnMoreURL: learnMoreURL)
case .verificationViolation(member: let member, learnMoreURL: let learnMoreURL):
verificationViolation(member: member, learnMoreURL: learnMoreURL)
case .historyVisible(learnMoreURL: let learnMoreURL):
historyVisibleAlert(learnMoreURL: learnMoreURL)
}
}

Expand Down Expand Up @@ -151,10 +153,43 @@ struct RoomScreenFooterView: View {
return description
}

private func historyVisibleAlertDescriptionWithLearnMoreLink(learnMoreURL: URL) -> AttributedString {
let linkPlaceholder = "{link}"
var description = AttributedString(L10n.cryptoHistoryVisible(linkPlaceholder))
var linkString = AttributedString(L10n.actionLearnMore)
linkString.link = learnMoreURL
linkString.bold()
description.replace(linkPlaceholder, with: linkString)
return description
}

private func fallbackDisplayName(_ userID: String) -> String {
guard let localpart = userID.components(separatedBy: ":").first else { return userID }
return String(localpart.trimmingPrefix("@"))
}

private func historyVisibleAlert(learnMoreURL: URL) -> some View {
let description = historyVisibleAlertDescriptionWithLearnMoreLink(learnMoreURL: learnMoreURL)

return VStack(spacing: 16) {
HStack(spacing: 16) {
CompoundIcon(\.info).foregroundColor(.compound.iconInfoPrimary)
Text(description)
.font(.compound.bodyMD)
.foregroundColor(.compound.textInfoPrimary)
}
Button {
callback(.dismissHistoryVisibleAlert)
} label: {
Text(L10n.actionDismiss)
.frame(maxWidth: .infinity)
}
.buttonStyle(.compound(.primary, size: .medium))
}
.padding(.top, 16)
.padding(.horizontal, 16)
.padding(.bottom, 8)
}
}

struct RoomScreenFooterView_Previews: PreviewProvider, TestablePreview {
Expand All @@ -166,12 +201,15 @@ struct RoomScreenFooterView_Previews: PreviewProvider, TestablePreview {
static let verificationViolationDetails: RoomScreenFooterViewDetails = .verificationViolation(member: RoomMemberProxyMock.mockBob,
learnMoreURL: "https://element.io/")

static let historyVisibleDetails: RoomScreenFooterViewDetails = .historyVisible(learnMoreURL: "https://element.io")

static var previews: some View {
RoomScreenFooterView(details: bobDetails, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
.previewDisplayName("With displayname")
RoomScreenFooterView(details: noNameDetails, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
.previewDisplayName("Without displayname")
RoomScreenFooterView(details: verificationViolationDetails, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
.previewDisplayName("Verification Violation")
RoomScreenFooterView(details: historyVisibleDetails, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }.previewDisplayName("History Visible")
}
}
1 change: 1 addition & 0 deletions ElementX/Sources/UITests/UITestsAppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class MockScreen: Identifiable {
deviceVerificationURL: appSettings.deviceVerificationURL,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL,
historyVisibleDetailsURL: appSettings.historyVisibleDetailsURL,
elementWebHosts: appSettings.elementWebHosts,
accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID,
Expand Down
2 changes: 1 addition & 1 deletion Enterprise
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class AuthenticationStartScreenViewModelTests: XCTestCase {
deviceVerificationURL: appSettings.deviceVerificationURL,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL,
historyVisibleDetailsURL: appSettings.historyVisibleDetailsURL,
elementWebHosts: appSettings.elementWebHosts,
accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID,
Expand Down
Loading
Loading