mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Stacked Avatars View (#3504)
* stacked avatars * fix tests * remove comment
This commit is contained in:
@@ -46,6 +46,7 @@ enum UserAvatarSizeOnScreen {
|
||||
case editUserDetails
|
||||
case suggestions
|
||||
case blockedUsers
|
||||
case knockingUsers
|
||||
|
||||
var value: CGFloat {
|
||||
switch self {
|
||||
@@ -75,6 +76,8 @@ enum UserAvatarSizeOnScreen {
|
||||
return 96
|
||||
case .dmDetails:
|
||||
return 75
|
||||
case .knockingUsers:
|
||||
return 28
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct StackedAvatarInfo {
|
||||
let url: URL?
|
||||
let name: String?
|
||||
let contentID: String
|
||||
}
|
||||
|
||||
struct StackedAvatarsView: View {
|
||||
let overlap: CGFloat
|
||||
let lineWidth: CGFloat
|
||||
var shouldStackFromLast = false
|
||||
let avatars: [StackedAvatarInfo]
|
||||
let avatarSize: AvatarSize
|
||||
let mediaProvider: MediaProviderProtocol?
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: -overlap) {
|
||||
ForEach(0..<avatars.count, id: \.self) { index in
|
||||
LoadableAvatarImage(url: avatars[index].url,
|
||||
name: avatars[index].name,
|
||||
contentID: avatars[index].contentID,
|
||||
avatarSize: avatarSize,
|
||||
mediaProvider: mediaProvider)
|
||||
.padding(lineWidth)
|
||||
.overlay {
|
||||
Circle()
|
||||
.strokeBorder(Color.compound.bgCanvasDefault, lineWidth: lineWidth)
|
||||
}
|
||||
.zIndex(shouldStackFromLast ? Double(index) : Double(avatars.count - index))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StackedAvatarsView_Previews: PreviewProvider, TestablePreview {
|
||||
static let avatars: [StackedAvatarInfo] = [
|
||||
.init(url: nil, name: "Alice", contentID: "@alice:matrix.org"),
|
||||
.init(url: nil, name: "Bob", contentID: "@bob:matrix.org"),
|
||||
.init(url: nil, name: "Charlie", contentID: "@charlie:matrix.org"),
|
||||
.init(url: nil, name: "Dan", contentID: "@charlie:matrix.org")
|
||||
]
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 10) {
|
||||
StackedAvatarsView(overlap: 16,
|
||||
lineWidth: 2,
|
||||
avatars: avatars,
|
||||
avatarSize: .user(on: .knockingUsers),
|
||||
mediaProvider: MediaProviderMock())
|
||||
StackedAvatarsView(overlap: 16,
|
||||
lineWidth: 2,
|
||||
shouldStackFromLast: true,
|
||||
avatars: avatars,
|
||||
avatarSize: .user(on: .knockingUsers),
|
||||
mediaProvider: MediaProviderMock())
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-16
@@ -11,25 +11,22 @@ struct TimelineReadReceiptsView: View {
|
||||
let displayNumber = 3
|
||||
let timelineItem: EventBasedTimelineItemProtocol
|
||||
@EnvironmentObject private var context: TimelineViewModel.Context
|
||||
|
||||
var avatars: [StackedAvatarInfo] {
|
||||
timelineItem.properties.orderedReadReceipts.prefix(displayNumber).map { receipt in
|
||||
StackedAvatarInfo(url: context.viewState.members[receipt.userID]?.avatarURL,
|
||||
name: context.viewState.members[receipt.userID]?.displayName,
|
||||
contentID: receipt.userID)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 2) {
|
||||
HStack(spacing: -4) {
|
||||
let receiptsToDisplay = timelineItem.properties.orderedReadReceipts.prefix(displayNumber)
|
||||
ForEach(0..<receiptsToDisplay.count, id: \.self) { index in
|
||||
let receipt = receiptsToDisplay[index]
|
||||
LoadableAvatarImage(url: context.viewState.members[receipt.userID]?.avatarURL,
|
||||
name: context.viewState.members[receipt.userID]?.displayName,
|
||||
contentID: receipt.userID,
|
||||
avatarSize: .user(on: .readReceipt),
|
||||
mediaProvider: context.mediaProvider)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: .infinity)
|
||||
.stroke(Color.compound.bgCanvasDefault, lineWidth: 1)
|
||||
}
|
||||
.zIndex(Double(displayNumber - index))
|
||||
}
|
||||
}
|
||||
StackedAvatarsView(overlap: 6,
|
||||
lineWidth: 1,
|
||||
avatars: avatars, avatarSize: .user(on: .readReceipt),
|
||||
mediaProvider: context.mediaProvider)
|
||||
.padding(-1)
|
||||
if timelineItem.properties.orderedReadReceipts.count > displayNumber {
|
||||
Text("+\(remaining)")
|
||||
.font(.compound.bodySM)
|
||||
|
||||
Reference in New Issue
Block a user