* Update dependency element-hq/compound-design-tokens to v6.10.0 * Further Compound updates. The gradients have been simplified and are now opaque. * Missed snapshots. --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Doug <douglase@element.io>
176 lines
7.6 KiB
Swift
176 lines
7.6 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2024-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
|
|
|
|
struct RoomScreenFooterView: View {
|
|
let details: RoomScreenFooterViewDetails?
|
|
let mediaProvider: MediaProviderProtocol?
|
|
let callback: (RoomScreenFooterViewAction) -> Void
|
|
|
|
private var borderColor: Color {
|
|
switch details {
|
|
case .pinViolation:
|
|
.compound.borderInfoSubtle
|
|
case .verificationViolation:
|
|
.compound.borderCriticalSubtle
|
|
case .none:
|
|
Color.compound.bgCanvasDefault
|
|
}
|
|
}
|
|
|
|
private var gradient: Gradient {
|
|
switch details {
|
|
case .pinViolation:
|
|
.compound.info
|
|
case .verificationViolation:
|
|
.compound.critical
|
|
case .none:
|
|
Gradient(colors: [.clear])
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
if let details {
|
|
detailsView(details)
|
|
.highlight(gradient: gradient, borderColor: borderColor)
|
|
.padding(.top, 8)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func detailsView(_ details: RoomScreenFooterViewDetails) -> some View {
|
|
switch details {
|
|
case .pinViolation(let member, let learnMoreURL):
|
|
pinViolation(member: member, learnMoreURL: learnMoreURL)
|
|
case .verificationViolation(member: let member, learnMoreURL: let learnMoreURL):
|
|
verificationViolation(member: member, learnMoreURL: learnMoreURL)
|
|
}
|
|
}
|
|
|
|
private func pinViolation(member: RoomMemberProxyProtocol,
|
|
learnMoreURL: URL) -> some View {
|
|
VStack(spacing: 16) {
|
|
HStack(spacing: 16) {
|
|
LoadableAvatarImage(url: member.avatarURL,
|
|
name: member.disambiguatedDisplayName,
|
|
contentID: member.userID,
|
|
avatarSize: .user(on: .timeline),
|
|
mediaProvider: mediaProvider)
|
|
|
|
Text(pinViolationDescriptionWithLearnMoreLink(displayName: member.displayName,
|
|
userID: member.userID,
|
|
url: learnMoreURL))
|
|
.font(.compound.bodyMD)
|
|
.foregroundColor(.compound.textPrimary)
|
|
}
|
|
|
|
Button {
|
|
callback(.resolvePinViolation(userID: member.userID))
|
|
} label: {
|
|
Text(L10n.actionDismiss)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.compound(.primary, size: .medium))
|
|
}
|
|
.padding(.top, 16)
|
|
.padding(.horizontal, 16)
|
|
.padding(.bottom, 8)
|
|
}
|
|
|
|
private func verificationViolation(member: RoomMemberProxyProtocol,
|
|
learnMoreURL: URL) -> some View {
|
|
VStack(spacing: 16) {
|
|
HStack(spacing: 16) {
|
|
LoadableAvatarImage(url: member.avatarURL,
|
|
name: member.disambiguatedDisplayName,
|
|
contentID: member.userID,
|
|
avatarSize: .user(on: .timeline),
|
|
mediaProvider: mediaProvider)
|
|
|
|
Text(verificationViolationDescriptionWithLearnMoreLink(displayName: member.displayName,
|
|
userID: member.userID,
|
|
url: learnMoreURL))
|
|
.font(.compound.bodyMD)
|
|
.foregroundColor(.compound.textCriticalPrimary)
|
|
}
|
|
|
|
Button {
|
|
callback(.resolveVerificationViolation(userID: member.userID))
|
|
} label: {
|
|
Text(L10n.cryptoIdentityChangeWithdrawVerificationAction)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.compound(.primary, size: .medium))
|
|
}
|
|
.padding(.top, 16)
|
|
.padding(.horizontal, 16)
|
|
.padding(.bottom, 8)
|
|
}
|
|
|
|
private func pinViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL) -> AttributedString {
|
|
let userIDPlaceholder = "{mxid}"
|
|
let linkPlaceholder = "{link}"
|
|
let displayName = displayName ?? fallbackDisplayName(userID)
|
|
var description = AttributedString(L10n.cryptoIdentityChangePinViolationNew(displayName, userIDPlaceholder, linkPlaceholder))
|
|
|
|
var userIDString = AttributedString(L10n.cryptoIdentityChangePinViolationNewUserId(userID))
|
|
userIDString.bold()
|
|
description.replace(userIDPlaceholder, with: userIDString)
|
|
|
|
var linkString = AttributedString(L10n.actionLearnMore)
|
|
linkString.link = url
|
|
linkString.bold()
|
|
description.replace(linkPlaceholder, with: linkString)
|
|
return description
|
|
}
|
|
|
|
private func verificationViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL) -> AttributedString {
|
|
let userIDPlaceholder = "{mxid}"
|
|
let linkPlaceholder = "{link}"
|
|
let displayName = displayName ?? fallbackDisplayName(userID)
|
|
var description = AttributedString(L10n.cryptoIdentityChangeVerificationViolationNew(displayName, userIDPlaceholder, linkPlaceholder))
|
|
|
|
var userIDString = AttributedString(L10n.cryptoIdentityChangePinViolationNewUserId(userID))
|
|
userIDString.bold()
|
|
description.replace(userIDPlaceholder, with: userIDString)
|
|
|
|
var linkString = AttributedString(L10n.actionLearnMore)
|
|
linkString.link = url
|
|
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("@"))
|
|
}
|
|
}
|
|
|
|
struct RoomScreenFooterView_Previews: PreviewProvider, TestablePreview {
|
|
static let bobDetails: RoomScreenFooterViewDetails = .pinViolation(member: RoomMemberProxyMock.mockBob,
|
|
learnMoreURL: "https://element.io/")
|
|
static let noNameDetails: RoomScreenFooterViewDetails = .pinViolation(member: RoomMemberProxyMock.mockNoName,
|
|
learnMoreURL: "https://element.io/")
|
|
|
|
static let verificationViolationDetails: RoomScreenFooterViewDetails = .verificationViolation(member: RoomMemberProxyMock.mockBob,
|
|
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")
|
|
}
|
|
}
|