Knock requests banner (#3510)

* need to add strings

* preview tests

* code improvements

* fixed an error
This commit is contained in:
Mauro
2024-11-13 16:14:18 +01:00
committed by GitHub
parent b122b02bee
commit 03cb50e6e3
28 changed files with 317 additions and 39 deletions
@@ -0,0 +1,191 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Compound
import SwiftUI
struct KnockRequestInfo {
let displayName: String?
let avatarURL: URL?
let userID: String
let reason: String?
}
struct KnockRequestsBannerView: View {
let requests: [KnockRequestInfo]
let onDismiss: () -> Void
let onAccept: (String) -> Void
let onViewAll: () -> Void
var mediaProvider: MediaProviderProtocol?
var body: some View {
mainContent
.padding(16)
.background(.compound.bgCanvasDefault, in: RoundedRectangle(cornerRadius: 12))
.compositingGroup()
.shadow(color: Color(red: 0.11, green: 0.11, blue: 0.13).opacity(0.1), radius: 12, x: 0, y: 4)
.padding(.horizontal, 16)
}
@ViewBuilder
private var mainContent: some View {
if requests.count == 1 {
SingleKnockRequestBannerContent(request: requests[0], onDismiss: onDismiss, onAccept: onAccept, onViewAll: onViewAll)
} else if requests.count > 1 {
MultipleKnockRequestsBannerContent(requests: requests, onDismiss: onDismiss, onViewAll: onViewAll)
} else {
EmptyView()
}
}
}
private struct SingleKnockRequestBannerContent: View {
let request: KnockRequestInfo
let onDismiss: () -> Void
let onAccept: (String) -> Void
let onViewAll: () -> Void
var mediaProvider: MediaProviderProtocol?
var body: some View {
VStack(spacing: 14) {
header
if let reason = request.reason {
Text(reason)
.lineLimit(2)
.font(.compound.bodyMD)
.foregroundStyle(.compound.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
}
actions
}
}
private var header: some View {
HStack(spacing: 10) {
LoadableAvatarImage(url: request.avatarURL,
name: request.displayName,
contentID: request.userID,
avatarSize: .user(on: .knockingUser), mediaProvider: mediaProvider)
VStack(spacing: 0) {
HStack(alignment: .top, spacing: 0) {
Text(L10n.screenRoomSingleKnockRequestTitle(request.displayName ?? request.userID))
.lineLimit(2)
.font(.compound.bodyMDSemibold)
.foregroundStyle(.compound.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
KnockRequestsBannerDismissButton(onDismiss: onDismiss)
}
if request.displayName != nil {
Text(request.userID)
.lineLimit(2)
.font(.compound.bodySM)
.foregroundStyle(.compound.textSecondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
private var actions: some View {
HStack(spacing: 12) {
Button(L10n.screenRoomSingleKnockRequestViewButtonTitle, action: onViewAll)
.buttonStyle(.compound(.secondary))
Button(L10n.screenRoomSingleKnockRequestAcceptButtonTitle, action: {
onAccept(request.userID)
})
.buttonStyle(.compound(.primary))
}
.padding(.top, request.reason == nil ? 0 : 2)
.frame(maxWidth: .infinity)
}
}
private struct MultipleKnockRequestsBannerContent: View {
let requests: [KnockRequestInfo]
let onDismiss: () -> Void
let onViewAll: () -> Void
var mediaProvider: MediaProviderProtocol?
private var avatars: [StackedAvatarInfo] {
requests
.prefix(3)
.map { .init(url: $0.avatarURL, name: $0.displayName, contentID: $0.userID) }
.reversed()
}
private var multipleKnockRequestsTitle: String {
guard let first = requests.first else {
return ""
}
let string = first.displayName ?? first.userID
return L10n.tr("Localizable", "screen_room_multiple_knock_requests_title", string, avatars.count - 1)
}
var body: some View {
VStack(spacing: 14) {
HStack(spacing: 10) {
StackedAvatarsView(overlap: 16, lineWidth: 2, shouldStackFromLast: true, avatars: avatars, avatarSize: .user(on: .knockingUsersStack), mediaProvider: mediaProvider)
HStack(alignment: .top, spacing: 0) {
Text(multipleKnockRequestsTitle)
.lineLimit(2)
.font(.compound.bodyMDSemibold)
.foregroundStyle(.compound.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
KnockRequestsBannerDismissButton(onDismiss: onDismiss)
}
}
Button(L10n.screenRoomMultipleKnockRequestsViewAllButtonTitle) {
onViewAll()
}
.buttonStyle(.compound(.primary))
}
}
}
private struct KnockRequestsBannerDismissButton: View {
let onDismiss: () -> Void
var body: some View {
Button {
onDismiss()
} label: {
CompoundIcon(\.close, size: .medium, relativeTo: .compound.bodySMSemibold)
.foregroundColor(.compound.iconTertiary)
}
.alignmentGuide(.top, computeValue: { _ in
3
})
}
}
struct KnockRequestsBannerView_Previews: PreviewProvider, TestablePreview {
static let singleRequest: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil)]
static let singleRequestWithReason: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hey, I’d like to join this room because of xyz topic and I’d like to participate in the room.")]
static let singleRequestNoDisplayName: [KnockRequestInfo] = [.init(displayName: nil, avatarURL: nil, userID: "@alice:matrix.org", reason: nil)]
static let multipleRequests: [KnockRequestInfo] = [
.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil),
.init(displayName: "Bob", avatarURL: nil, userID: "@bob:matrix.org", reason: nil),
.init(displayName: "Charlie", avatarURL: nil, userID: "@charlie:matrix.org", reason: nil),
.init(displayName: "Dan", avatarURL: nil, userID: "@dan:matrix.org", reason: nil),
.init(displayName: "Test", avatarURL: nil, userID: "@dan:matrix.org", reason: nil)
]
static var previews: some View {
KnockRequestsBannerView(requests: singleRequest, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
.previewDisplayName("Single Request")
KnockRequestsBannerView(requests: singleRequestWithReason, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
.previewDisplayName("Single Request with reason")
KnockRequestsBannerView(requests: singleRequestNoDisplayName, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
.previewDisplayName("Single Request, No Display Name")
KnockRequestsBannerView(requests: multipleRequests, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
.previewDisplayName("Multiple Requests")
}
}