Use the room heroes when computing a DM avatar. (#2900)

* Use room heroes for DM avatar content ID.

* Use RoomAvatar.heroes for the DM Details stack.
This commit is contained in:
Doug
2024-06-19 11:27:10 +01:00
committed by GitHub
parent 17d96b0f4b
commit 834260c144
55 changed files with 524 additions and 198 deletions
@@ -0,0 +1,125 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
/// Information about a room avatar such as it's URL or the heroes to use as a fallback.
enum RoomAvatar: Equatable {
/// An avatar generated from the room's details.
case room(id: String, name: String?, avatarURL: URL?)
/// An avatar generated from the room's heroes.
case heroes([UserProfileProxy])
}
/// A view that shows the avatar for a room, or a cluster of heroes if provided.
///
/// This should be preferred over `LoadableAvatarImage` when displaying a
/// room avatar so that DMs have a consistent appearance throughout the app.
struct RoomAvatarImage: View {
let avatar: RoomAvatar
let avatarSize: AvatarSize
let imageProvider: ImageProviderProtocol?
var body: some View {
switch avatar {
case .room(let id, let name, let avatarURL):
LoadableAvatarImage(url: avatarURL,
name: name,
contentID: id,
avatarSize: avatarSize,
imageProvider: imageProvider)
case .heroes(let users):
// We will expand upon this with more stack sizes in the future.
if users.count == 0 {
let _ = assertionFailure("We should never pass empty heroes here.")
PlaceholderAvatarImage(name: nil, contentID: nil)
} else if users.count == 2 {
let clusterSize = avatarSize.value * 1.6
ZStack {
LoadableAvatarImage(url: users[0].avatarURL,
name: users[0].displayName,
contentID: users[0].userID,
avatarSize: avatarSize,
imageProvider: imageProvider)
.scaledFrame(size: clusterSize, alignment: .topTrailing)
LoadableAvatarImage(url: users[1].avatarURL,
name: users[1].displayName,
contentID: users[1].userID,
avatarSize: avatarSize,
imageProvider: imageProvider)
.mask {
Rectangle()
.fill(Color.white)
.overlay {
Circle()
.inset(by: -4)
.fill(Color.black)
.scaledOffset(x: clusterSize - avatarSize.value,
y: -clusterSize + avatarSize.value)
}
.compositingGroup()
.luminanceToAlpha()
}
.scaledFrame(size: clusterSize, alignment: .bottomLeading)
}
.scaledFrame(size: clusterSize)
} else {
LoadableAvatarImage(url: users[0].avatarURL,
name: users[0].displayName,
contentID: users[0].userID,
avatarSize: avatarSize,
imageProvider: imageProvider)
}
}
}
}
struct RoomAvatarImage_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
HStack(spacing: 8) {
RoomAvatarImage(avatar: .room(id: "!1:server.com",
name: "Room",
avatarURL: nil),
avatarSize: .room(on: .home),
imageProvider: MockMediaProvider())
RoomAvatarImage(avatar: .room(id: "!2:server.com",
name: "Room",
avatarURL: .picturesDirectory),
avatarSize: .room(on: .home),
imageProvider: MockMediaProvider())
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
displayName: "User",
avatarURL: nil)]),
avatarSize: .room(on: .home),
imageProvider: MockMediaProvider())
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
displayName: "User",
avatarURL: .picturesDirectory)]),
avatarSize: .room(on: .home),
imageProvider: MockMediaProvider())
RoomAvatarImage(avatar: .heroes([.init(userID: "@alice:server.com", displayName: "Alice", avatarURL: nil),
.init(userID: "@bob:server.net", displayName: "Bob", avatarURL: nil)]),
avatarSize: .room(on: .home),
imageProvider: MockMediaProvider())
}
}
}