mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Show user statuses in AvatarHeaderView.
Also tweaks the layout to more closely match Figma.
This commit is contained in:
@@ -25,6 +25,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
|
|
||||||
private let avatarInfo: AvatarInfo
|
private let avatarInfo: AvatarInfo
|
||||||
private let title: String
|
private let title: String
|
||||||
|
private let status: UserStatus?
|
||||||
private let subtitle: String?
|
private let subtitle: String?
|
||||||
private let badges: [Badge]
|
private let badges: [Badge]
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
@ViewBuilder footer: @escaping () -> Footer) {
|
@ViewBuilder footer: @escaping () -> Footer) {
|
||||||
avatarInfo = .room(room.avatar)
|
avatarInfo = .room(room.avatar)
|
||||||
title = room.name ?? room.id
|
title = room.name ?? room.id
|
||||||
|
status = nil
|
||||||
|
|
||||||
if let roomAlias = room.canonicalAlias {
|
if let roomAlias = room.canonicalAlias {
|
||||||
subtitle = roomAlias
|
subtitle = roomAlias
|
||||||
@@ -74,6 +76,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
avatarInfo = .room(.heroes([dmRecipientProfile, UserProfile(member: accountOwner)]))
|
avatarInfo = .room(.heroes([dmRecipientProfile, UserProfile(member: accountOwner)]))
|
||||||
title = dmRecipientProfile.displayName ?? dmRecipientProfile.id
|
title = dmRecipientProfile.displayName ?? dmRecipientProfile.id
|
||||||
subtitle = dmRecipientProfile.displayName == nil ? nil : dmRecipientProfile.id
|
subtitle = dmRecipientProfile.displayName == nil ? nil : dmRecipientProfile.id
|
||||||
|
status = dmRecipientProfile.status
|
||||||
|
|
||||||
avatarSize = .user(on: .dmDetails)
|
avatarSize = .user(on: .dmDetails)
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
@@ -107,6 +110,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
@ViewBuilder footer: @escaping () -> Footer) {
|
@ViewBuilder footer: @escaping () -> Footer) {
|
||||||
avatarInfo = .user(user)
|
avatarInfo = .user(user)
|
||||||
title = user.displayName ?? user.id
|
title = user.displayName ?? user.id
|
||||||
|
status = user.status
|
||||||
subtitle = user.displayName == nil ? nil : user.id
|
subtitle = user.displayName == nil ? nil : user.id
|
||||||
|
|
||||||
self.avatarSize = avatarSize
|
self.avatarSize = avatarSize
|
||||||
@@ -133,6 +137,84 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
footer: footer)
|
footer: footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
avatar
|
||||||
|
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Text(title)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.font(.compound.headingMDBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
|
||||||
|
if let displayedStatus = status?.displayed {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(String(displayedStatus.emoji))
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
Text(displayedStatus.text)
|
||||||
|
.foregroundStyle(.compound.textSecondary)
|
||||||
|
}
|
||||||
|
.font(.compound.bodyMDSemibold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.foregroundStyle(.compound.textSecondary)
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !badges.isEmpty {
|
||||||
|
badgesStack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
.listRowInsets(EdgeInsets(top: 11,
|
||||||
|
leading: 0,
|
||||||
|
bottom: 11,
|
||||||
|
trailing: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var avatar: some View {
|
||||||
|
switch avatarInfo {
|
||||||
|
case .room(let roomAvatar):
|
||||||
|
RoomAvatarImage(avatar: roomAvatar,
|
||||||
|
avatarSize: avatarSize,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
onAvatarTap: onAvatarTap)
|
||||||
|
.accessibilityLabel(avatarAccessibilityLabel)
|
||||||
|
|
||||||
|
case .user(let userProfile):
|
||||||
|
LoadableAvatarImage(url: userProfile.avatarURL,
|
||||||
|
name: userProfile.displayName,
|
||||||
|
contentID: userProfile.id,
|
||||||
|
avatarSize: avatarSize,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
onTap: onAvatarTap)
|
||||||
|
.accessibilityLabel(avatarAccessibilityLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarAccessibilityLabel: String {
|
||||||
|
guard onAvatarTap != nil else {
|
||||||
|
return L10n.a11yAvatar
|
||||||
|
}
|
||||||
|
switch avatarInfo {
|
||||||
|
case .room(let roomAvatar):
|
||||||
|
return roomAvatar.hasURL ? L10n.a11yViewAvatar : L10n.a11yAvatar
|
||||||
|
case .user(let userProfile):
|
||||||
|
return userProfile.avatarURL != nil ? L10n.a11yViewAvatar : L10n.a11yAvatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var badgesStack: some View {
|
private var badgesStack: some View {
|
||||||
HFlow(horizontalAlignment: .center,
|
HFlow(horizontalAlignment: .center,
|
||||||
verticalAlignment: .top,
|
verticalAlignment: .top,
|
||||||
@@ -172,74 +254,6 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var avatarAccessibilityLabel: String {
|
|
||||||
guard onAvatarTap != nil else {
|
|
||||||
return L10n.a11yAvatar
|
|
||||||
}
|
|
||||||
switch avatarInfo {
|
|
||||||
case .room(let roomAvatar):
|
|
||||||
return roomAvatar.hasURL ? L10n.a11yViewAvatar : L10n.a11yAvatar
|
|
||||||
case .user(let userProfile):
|
|
||||||
return userProfile.avatarURL != nil ? L10n.a11yViewAvatar : L10n.a11yAvatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var avatar: some View {
|
|
||||||
switch avatarInfo {
|
|
||||||
case .room(let roomAvatar):
|
|
||||||
RoomAvatarImage(avatar: roomAvatar,
|
|
||||||
avatarSize: avatarSize,
|
|
||||||
mediaProvider: mediaProvider,
|
|
||||||
onAvatarTap: onAvatarTap)
|
|
||||||
.accessibilityLabel(avatarAccessibilityLabel)
|
|
||||||
|
|
||||||
case .user(let userProfile):
|
|
||||||
LoadableAvatarImage(url: userProfile.avatarURL,
|
|
||||||
name: userProfile.displayName,
|
|
||||||
contentID: userProfile.id,
|
|
||||||
avatarSize: avatarSize,
|
|
||||||
mediaProvider: mediaProvider,
|
|
||||||
onTap: onAvatarTap)
|
|
||||||
.accessibilityLabel(avatarAccessibilityLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 8.0) {
|
|
||||||
avatar
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
.frame(height: 9)
|
|
||||||
|
|
||||||
Text(title)
|
|
||||||
.foregroundColor(.compound.textPrimary)
|
|
||||||
.font(.compound.headingMDBold)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
|
|
||||||
if let subtitle {
|
|
||||||
Text(subtitle)
|
|
||||||
.foregroundColor(.compound.textSecondary)
|
|
||||||
.font(.compound.bodyLG)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !badges.isEmpty {
|
|
||||||
badgesStack
|
|
||||||
}
|
|
||||||
|
|
||||||
footer()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
|
||||||
.listRowBackground(Color.clear)
|
|
||||||
.listRowInsets(EdgeInsets(top: 11,
|
|
||||||
leading: 0,
|
|
||||||
bottom: 11,
|
|
||||||
trailing: 0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
||||||
@@ -263,7 +277,6 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
||||||
}
|
}
|
||||||
.padding(.top, 32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.previewDisplayName("Room")
|
.previewDisplayName("Room")
|
||||||
@@ -277,7 +290,6 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
||||||
}
|
}
|
||||||
.padding(.top, 32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.previewDisplayName("DM")
|
.previewDisplayName("DM")
|
||||||
@@ -292,6 +304,16 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
|||||||
avatarSize: .room(on: .details),
|
avatarSize: .room(on: .details),
|
||||||
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
||||||
|
|
||||||
|
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockErin),
|
||||||
|
isVerified: false,
|
||||||
|
avatarSize: .room(on: .details),
|
||||||
|
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
||||||
|
|
||||||
|
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockFrank),
|
||||||
|
isVerified: false,
|
||||||
|
avatarSize: .room(on: .details),
|
||||||
|
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
||||||
|
|
||||||
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
||||||
avatarSize: .room(on: .details),
|
avatarSize: .room(on: .details),
|
||||||
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
mediaProvider: MediaProviderMock(.init())) { Text("") }
|
||||||
@@ -326,7 +348,6 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
||||||
}
|
}
|
||||||
.padding(.top, 32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ struct RoomDetailsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 32)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
|
|
||||||
otherUserFooter
|
otherUserFooter
|
||||||
}
|
}
|
||||||
.padding(.top, 24)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AvatarHeaderView(user: UserProfile(userID: context.viewState.userID),
|
AvatarHeaderView(user: UserProfile(userID: context.viewState.userID),
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ struct UserProfileScreen: View {
|
|||||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
|
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 32)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
|
|||||||
Reference in New Issue
Block a user