mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Add support for Space avatars. (#4362)
This commit is contained in:
@@ -11,20 +11,24 @@ struct LoadableAvatarImage: View {
|
||||
private let url: URL?
|
||||
private let name: String?
|
||||
private let contentID: String?
|
||||
private let isSpace: Bool
|
||||
private let avatarSize: Avatars.Size
|
||||
private let mediaProvider: MediaProviderProtocol?
|
||||
private let onTap: ((URL) -> Void)?
|
||||
|
||||
@ScaledMetric private var frameSize: CGFloat
|
||||
|
||||
init(url: URL?, name: String?,
|
||||
init(url: URL?,
|
||||
name: String?,
|
||||
contentID: String?,
|
||||
isSpace: Bool = false,
|
||||
avatarSize: Avatars.Size,
|
||||
mediaProvider: MediaProviderProtocol?,
|
||||
onTap: ((URL) -> Void)? = nil) {
|
||||
self.url = url
|
||||
self.name = name
|
||||
self.contentID = contentID
|
||||
self.isSpace = isSpace
|
||||
self.avatarSize = avatarSize
|
||||
self.mediaProvider = mediaProvider
|
||||
self.onTap = onTap
|
||||
@@ -49,10 +53,14 @@ struct LoadableAvatarImage: View {
|
||||
avatar
|
||||
.frame(width: frameSize, height: frameSize)
|
||||
.background(Color.compound.bgCanvasDefault)
|
||||
.clipShape(Circle())
|
||||
.clipShape(avatarShape)
|
||||
.environment(\.shouldAutomaticallyLoadImages, true) // We always load avatars.
|
||||
}
|
||||
|
||||
private var avatarShape: some Shape {
|
||||
isSpace ? AnyShape(RoundedRectangle(cornerRadius: frameSize / 4)) : AnyShape(Circle())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var avatar: some View {
|
||||
if let url {
|
||||
|
||||
@@ -9,10 +9,12 @@ 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.
|
||||
/// An avatar generated from a Room's details.
|
||||
case room(id: String, name: String?, avatarURL: URL?)
|
||||
/// An avatar generated from the room's heroes.
|
||||
/// An avatar generated from a collection of room heroes.
|
||||
case heroes([UserProfileProxy])
|
||||
/// An avatar generated from a Space's details.
|
||||
case space(id: String, name: String?, avatarURL: URL?)
|
||||
/// A static avatar for a tombstoned room.
|
||||
case tombstoned
|
||||
|
||||
@@ -22,6 +24,8 @@ enum RoomAvatar: Equatable {
|
||||
.room(id: id, name: name, avatarURL: nil)
|
||||
case let .heroes(users):
|
||||
.heroes(users.map { .init(userID: $0.userID, displayName: $0.displayName, avatarURL: nil) })
|
||||
case .space(let id, let name, _):
|
||||
.space(id: id, name: name, avatarURL: nil)
|
||||
case .tombstoned:
|
||||
.tombstoned
|
||||
}
|
||||
@@ -29,7 +33,8 @@ enum RoomAvatar: Equatable {
|
||||
|
||||
var hasURL: Bool {
|
||||
switch self {
|
||||
case let .room(_, _, url):
|
||||
case let .room(_, _, url),
|
||||
let .space(_, _, url):
|
||||
return url != nil
|
||||
case let .heroes(heroes):
|
||||
return heroes.first?.avatarURL != nil
|
||||
@@ -106,6 +111,14 @@ struct RoomAvatarImage: View {
|
||||
mediaProvider: mediaProvider,
|
||||
onTap: onAvatarTap)
|
||||
}
|
||||
case .space(let id, let name, let avatarURL):
|
||||
LoadableAvatarImage(url: avatarURL,
|
||||
name: name,
|
||||
contentID: id,
|
||||
isSpace: true,
|
||||
avatarSize: avatarSize,
|
||||
mediaProvider: mediaProvider,
|
||||
onTap: onAvatarTap)
|
||||
case .tombstoned:
|
||||
TombstonedAvatarImage(avatarSize: avatarSize)
|
||||
}
|
||||
@@ -114,37 +127,53 @@ struct RoomAvatarImage: View {
|
||||
|
||||
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),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
VStack(spacing: 20) {
|
||||
HStack(spacing: 12) {
|
||||
RoomAvatarImage(avatar: .room(id: "!1:server.com",
|
||||
name: "Room",
|
||||
avatarURL: nil),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .room(id: "!2:server.com",
|
||||
name: "Room",
|
||||
avatarURL: .mockMXCAvatar),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .space(id: "!space:server.com",
|
||||
name: "Room",
|
||||
avatarURL: nil),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .space(id: "!otherspace:server.com",
|
||||
name: "Room",
|
||||
avatarURL: .mockMXCAvatar),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .tombstoned, avatarSize: .room(on: .home), mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
}
|
||||
|
||||
RoomAvatarImage(avatar: .room(id: "!2:server.com",
|
||||
name: "Room",
|
||||
avatarURL: .mockMXCAvatar),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
|
||||
displayName: "User",
|
||||
avatarURL: nil)]),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
|
||||
displayName: "User",
|
||||
avatarURL: .mockMXCAvatar)]),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
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),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .tombstoned, avatarSize: .room(on: .home), mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
HStack(spacing: 12) {
|
||||
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
|
||||
displayName: "User",
|
||||
avatarURL: nil)]),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
RoomAvatarImage(avatar: .heroes([.init(userID: "@user:server.com",
|
||||
displayName: "User",
|
||||
avatarURL: .mockMXCAvatar)]),
|
||||
avatarSize: .room(on: .home),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
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),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ extension BaseRoomInfoProxyProtocol {
|
||||
return .tombstoned
|
||||
}
|
||||
|
||||
if isSpace {
|
||||
return .space(id: id, name: displayName, avatarURL: avatarURL)
|
||||
}
|
||||
|
||||
if isDirect, avatarURL == nil, heroes.count == 1 {
|
||||
return .heroes(heroes.map(UserProfileProxy.init))
|
||||
}
|
||||
|
||||
@@ -134,6 +134,11 @@ extension RoomSummary {
|
||||
return .tombstoned
|
||||
}
|
||||
|
||||
// NOTE: The check for isSpace isn't implemented yet, waiting to see
|
||||
// whether we end up with a different type for spaces in the room list.
|
||||
//
|
||||
// Don't forget to add a test when you remove this comment 😄
|
||||
|
||||
if isDirect, avatarURL == nil, heroes.count == 1 {
|
||||
return .heroes(heroes)
|
||||
} else {
|
||||
|
||||
@@ -570,7 +570,8 @@ class TimelineController: TimelineControllerProtocol {
|
||||
attachments: nil)
|
||||
|
||||
let avatarURL: URL? = switch roomProxy.details.avatar {
|
||||
case .room(_, _, let avatarURL):
|
||||
case .room(_, _, let avatarURL),
|
||||
.space(_, _, let avatarURL):
|
||||
avatarURL
|
||||
case .heroes(let userProfiles):
|
||||
userProfiles.first?.avatarURL
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1896e5328d738494310900bf5d0f4a7441bb70f91103a20bce20ca064b6e6d5e
|
||||
size 103499
|
||||
oid sha256:e28d36588f155e868a9b24d78f7688c64fac91e57a81b835dd111687c265428b
|
||||
size 132520
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1896e5328d738494310900bf5d0f4a7441bb70f91103a20bce20ca064b6e6d5e
|
||||
size 103499
|
||||
oid sha256:e28d36588f155e868a9b24d78f7688c64fac91e57a81b835dd111687c265428b
|
||||
size 132520
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d84daf5a9f2f6caed69fca648e8f677b3a1055f238d482bbfd7ede5769605bb6
|
||||
size 58978
|
||||
oid sha256:4d5acb74a871dc05d9112d6c5ee88d26f2ed2a489995bf9800f9c644236576f9
|
||||
size 85999
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d84daf5a9f2f6caed69fca648e8f677b3a1055f238d482bbfd7ede5769605bb6
|
||||
size 58978
|
||||
oid sha256:4d5acb74a871dc05d9112d6c5ee88d26f2ed2a489995bf9800f9c644236576f9
|
||||
size 85999
|
||||
|
||||
@@ -24,6 +24,8 @@ class RoomSummaryTests: XCTestCase {
|
||||
XCTAssertEqual(avatarURL, roomDetails.avatarURL)
|
||||
case .heroes:
|
||||
XCTFail("A room shouldn't use the heroes for its avatar.")
|
||||
case .space:
|
||||
XCTFail("A room shouldn't use a space avatar.")
|
||||
case .tombstoned:
|
||||
XCTFail("A room shouldn't use the tombstone for its avatar.")
|
||||
}
|
||||
@@ -39,6 +41,8 @@ class RoomSummaryTests: XCTestCase {
|
||||
XCTAssertEqual(avatarURL, roomDetails.avatarURL)
|
||||
case .heroes:
|
||||
XCTFail("A DM with an avatar set shouldn't use the heroes instead.")
|
||||
case .space:
|
||||
XCTFail("A DM shouldn't use a space avatar.")
|
||||
case .tombstoned:
|
||||
XCTFail("A room shouldn't use the tombstone for its avatar.")
|
||||
}
|
||||
@@ -52,6 +56,8 @@ class RoomSummaryTests: XCTestCase {
|
||||
XCTFail("A DM without an avatar should defer to the hero for the correct placeholder tint colour.")
|
||||
case .heroes(let heroes):
|
||||
XCTAssertEqual(heroes, self.heroes)
|
||||
case .space:
|
||||
XCTFail("A DM shouldn't use a space avatar.")
|
||||
case .tombstoned:
|
||||
XCTFail("A room shouldn't use the tombstone for its avatar.")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user