1. I removed the grey dots for when there is activity in rooms set to mentions or mute. 2. For all unread rooms, I made the message preview text bold for better legibility 3. For rooms with no unreads, I made the font weight regular, again for legibility We have also mergeg the new room list activity rendering with the existing feature hide unread badge feature flag and provide 3 variations: current production behavior, no badge but bolding, and no bolding and no badge
282 lines
11 KiB
Swift
282 lines
11 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2024-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Combine
|
|
import Compound
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
struct HomeScreenInviteCell: View {
|
|
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
|
|
|
let room: HomeScreenRoom
|
|
let context: HomeScreenViewModel.Context
|
|
let hideInviteAvatars: Bool
|
|
|
|
private var avatar: RoomAvatar {
|
|
// DM invites avatars are broken, this is a workaround
|
|
// https://github.com/matrix-org/matrix-rust-sdk/issues/4825
|
|
if room.isDirect, let inviter = room.inviter {
|
|
.heroes([.init(userID: inviter.id, displayName: inviter.displayName, avatarURL: hideInviteAvatars ? nil : inviter.avatarURL)])
|
|
} else {
|
|
hideInviteAvatars ? room.avatar.removingAvatar : room.avatar
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 16) {
|
|
if dynamicTypeSize < .accessibility3 {
|
|
RoomAvatarImage(avatar: avatar,
|
|
avatarSize: .custom(52),
|
|
mediaProvider: context.mediaProvider)
|
|
.dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1)
|
|
.accessibilityHidden(true)
|
|
}
|
|
|
|
mainContent
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(.bottom, 16)
|
|
.padding(.trailing, 16)
|
|
.multilineTextAlignment(.leading)
|
|
.overlay(alignment: .bottom) {
|
|
separator
|
|
}
|
|
}
|
|
.padding(.top, 12)
|
|
.padding(.leading, 16)
|
|
.onTapGesture {
|
|
if let roomID = room.roomID {
|
|
context.send(viewAction: .selectRoom(roomIdentifier: roomID))
|
|
}
|
|
}
|
|
.accessibilityIdentifier(A11yIdentifiers.homeScreen.roomName(room.name))
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private var mainContent: some View {
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
HStack(alignment: .firstTextBaseline, spacing: 16) {
|
|
textualContent
|
|
badge
|
|
}
|
|
|
|
inviterView
|
|
.padding(.top, 6)
|
|
.padding(.trailing, 16)
|
|
}
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.accessibilityElement(children: .combine)
|
|
|
|
ViewThatFits {
|
|
HStack(spacing: 12) { buttons }
|
|
VStack(spacing: 16) { Group(subviews: buttons) { ForEach($0.reversed()) { $0 } } }
|
|
}
|
|
.padding(.trailing, 22)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var inviterView: some View {
|
|
if let inviter = room.inviter,
|
|
!room.isDirect {
|
|
RoomInviterLabel(inviter: inviter,
|
|
shouldHideAvatar: hideInviteAvatars,
|
|
mediaProvider: context.mediaProvider)
|
|
.font(.compound.bodyMD)
|
|
.foregroundStyle(.compound.textSecondary)
|
|
}
|
|
}
|
|
|
|
private var textualContent: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
Text(title)
|
|
.font(.compound.bodyLGSemibold)
|
|
.foregroundColor(.compound.textPrimary)
|
|
.lineLimit(2)
|
|
|
|
if let subtitle {
|
|
Text(subtitle)
|
|
.font(.compound.bodyMD)
|
|
.foregroundColor(.compound.textSecondary)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var buttons: some View {
|
|
Button {
|
|
context.send(viewAction: .declineInvite(roomIdentifier: room.id))
|
|
} label: {
|
|
Text(L10n.actionDecline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.compound(.secondary, size: .medium))
|
|
|
|
Button {
|
|
context.send(viewAction: .acceptInvite(roomIdentifier: room.id))
|
|
} label: {
|
|
Text(L10n.actionAccept)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.compound(.primary, size: .medium))
|
|
}
|
|
|
|
private var separator: some View {
|
|
Rectangle()
|
|
.fill(Color.compound.borderDisabled)
|
|
.frame(height: 1 / UIScreen.main.scale)
|
|
}
|
|
|
|
private var title: String {
|
|
room.name
|
|
}
|
|
|
|
private var subtitle: String? {
|
|
room.isDirect ? room.inviter?.id : nil
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var badge: some View {
|
|
if room.badges.isDotShown {
|
|
Circle()
|
|
.scaledFrame(size: 12)
|
|
.foregroundColor(.compound.iconAccentTertiary) // The badge is always green, no need to check isHighlighted here.
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
import MatrixRustSDKMocks
|
|
|
|
struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview {
|
|
static var previews: some View {
|
|
VStack(spacing: 0) {
|
|
HomeScreenInviteCell(room: .dmInvite,
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: false)
|
|
|
|
HomeScreenInviteCell(room: .invite(),
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: false)
|
|
|
|
HomeScreenInviteCell(room: .invite(alias: "#footest:somewhere.org",
|
|
avatarURL: .mockMXCAvatar),
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: false)
|
|
|
|
// Not the final design, may get its own cell type entirely.
|
|
HomeScreenInviteCell(room: .invite(name: "Awesome Space",
|
|
isSpace: true,
|
|
alias: "#footest:somewhere.org",
|
|
avatarURL: .mockMXCAvatar),
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: false)
|
|
|
|
HomeScreenInviteCell(room: .invite(name: "Hidden Avatars",
|
|
avatarURL: .mockMXCAvatar),
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: true)
|
|
|
|
HomeScreenInviteCell(room: .invite(alias: "#footest:somewhere.org"),
|
|
context: makeViewModel().context,
|
|
hideInviteAvatars: false)
|
|
.dynamicTypeSize(.accessibility1)
|
|
.previewDisplayName("Aliased room (AX1)")
|
|
}
|
|
.previewLayout(.sizeThatFits)
|
|
}
|
|
|
|
static func makeViewModel() -> HomeScreenViewModel {
|
|
let clientProxy = ClientProxyMock(.init())
|
|
|
|
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
|
|
|
return HomeScreenViewModel(userSession: userSession,
|
|
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
|
|
appSettings: ServiceLocator.shared.settings,
|
|
analyticsService: ServiceLocator.shared.analytics,
|
|
notificationManager: NotificationManagerMock(),
|
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private extension HomeScreenRoom {
|
|
static var dmInvite: HomeScreenRoom {
|
|
let inviter = RoomMemberProxyMock()
|
|
inviter.displayName = "Some Guy"
|
|
inviter.userID = "@someone:somewhere.com"
|
|
|
|
let summary = RoomSummary(room: RoomSDKMock(),
|
|
id: "@someone:somewhere.com",
|
|
joinRequestType: .invite(inviter: inviter),
|
|
name: "Some Guy",
|
|
isDirect: true,
|
|
isSpace: false,
|
|
avatarURL: nil,
|
|
heroes: [.init(userID: "@someone:somewhere.com")],
|
|
activeMembersCount: 0,
|
|
lastMessage: nil,
|
|
lastMessageDate: nil,
|
|
lastMessageState: nil,
|
|
unreadMessagesCount: 0,
|
|
unreadMentionsCount: 0,
|
|
unreadNotificationsCount: 0,
|
|
notificationMode: nil,
|
|
canonicalAlias: "#footest:somewhere.org",
|
|
alternativeAliases: [],
|
|
hasOngoingCall: false,
|
|
activeCallIntent: nil,
|
|
isMarkedUnread: false,
|
|
isFavourite: false,
|
|
isTombstoned: false)
|
|
|
|
return .init(summary: summary)
|
|
}
|
|
|
|
static func invite(name: String = "Awesome Room",
|
|
isSpace: Bool = false,
|
|
alias: String? = nil,
|
|
avatarURL: URL? = nil) -> HomeScreenRoom {
|
|
let inviter = RoomMemberProxyMock()
|
|
inviter.displayName = "Luca"
|
|
inviter.userID = "@jack:somewhi.nl"
|
|
inviter.avatarURL = avatarURL.map { _ in .mockMXCUserAvatar }
|
|
|
|
let summary = RoomSummary(room: RoomSDKMock(),
|
|
id: "@someone:somewhere.com",
|
|
joinRequestType: .invite(inviter: inviter),
|
|
name: name,
|
|
isDirect: false,
|
|
isSpace: isSpace,
|
|
avatarURL: avatarURL,
|
|
heroes: [.init(userID: "@someone:somewhere.com")],
|
|
activeMembersCount: 0,
|
|
lastMessage: nil,
|
|
lastMessageDate: nil,
|
|
lastMessageState: nil,
|
|
unreadMessagesCount: 0,
|
|
unreadMentionsCount: 0,
|
|
unreadNotificationsCount: 0,
|
|
notificationMode: nil,
|
|
canonicalAlias: alias,
|
|
alternativeAliases: [],
|
|
hasOngoingCall: false,
|
|
activeCallIntent: nil,
|
|
isMarkedUnread: false,
|
|
isFavourite: false,
|
|
isTombstoned: false)
|
|
|
|
return .init(summary: summary)
|
|
}
|
|
}
|