Skip to content

Commit

Permalink
Stacked Avatars View (#3504)
Browse files Browse the repository at this point in the history
* stacked avatars

* fix tests

* remove comment
  • Loading branch information
Velin92 authored Nov 12, 2024
1 parent 2b15330 commit f7aeb3e
Show file tree
Hide file tree
Showing 29 changed files with 144 additions and 56 deletions.
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@
E0FB26262689F04D66A949D7 /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E227F34BE43B08E098796E /* TestablePreview.swift */; };
E14E469CD97550D0FC58F3CA /* CancellableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52983FAFB4E0998C00EE8A /* CancellableTask.swift */; };
E184FFAD32342D3D6E2F89AA /* PinnedEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D53754227CEBD06358956D7 /* PinnedEventsTimelineScreenCoordinator.swift */; };
E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */; };
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
E21FE4C5B614F311C0955859 /* UserProfileProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */; };
E27C4D1A1F8BB77CA790B403 /* InviteUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */; };
Expand Down Expand Up @@ -1946,6 +1947,7 @@
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = "<group>"; };
A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = "<group>"; };
A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedAvatarsView.swift; sourceTree = "<group>"; };
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
A8DF55467ED4CE76B7AE9A33 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A9873374E72AA53260AE90A2 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2989,6 +2991,7 @@
7EB58E4E8D6D634C246AD5C2 /* RoomInviterLabel.swift */,
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */,
AEB5FF7A09B79B0C6B528F7C /* SFNumberedListView.swift */,
A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */,
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */,
AD529C89924EE32CE307F36F /* VisualListItem.swift */,
);
Expand Down Expand Up @@ -6952,6 +6955,7 @@
F37629BAA5E8F50AAF2A131D /* SoftLogoutScreenViewModel.swift in Sources */,
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */,
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */,
E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */,
3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */,
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */,
C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions ElementX/Sources/Other/AvatarSize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum UserAvatarSizeOnScreen {
case editUserDetails
case suggestions
case blockedUsers
case knockingUsers

var value: CGFloat {
switch self {
Expand Down Expand Up @@ -75,6 +76,8 @@ enum UserAvatarSizeOnScreen {
return 96
case .dmDetails:
return 75
case .knockingUsers:
return 28
}
}
}
Expand Down
66 changes: 66 additions & 0 deletions ElementX/Sources/Other/SwiftUI/Views/StackedAvatarsView.swift
Original file line number Diff line number Diff line change
@@ -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())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions PreviewTests/Sources/GeneratedPreviewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,12 @@ extension PreviewTests {
}
}

func test_stackedAvatarsView() {
for preview in StackedAvatarsView_Previews._allPreviews {
assertSnapshots(matching: preview)
}
}

func test_startChatScreen() {
for preview in StartChatScreen_Previews._allPreviews {
assertSnapshots(matching: preview)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f7aeb3e

Please sign in to comment.