mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Show user statuses in the DM room header.
This commit is contained in:
@@ -11,10 +11,15 @@ import Compound
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RoomHeaderView: View {
|
struct RoomHeaderView: View {
|
||||||
|
struct DMRecipientDetails {
|
||||||
|
var status: UserStatus?
|
||||||
|
var verification: UserIdentityVerificationState?
|
||||||
|
}
|
||||||
|
|
||||||
let roomName: String
|
let roomName: String
|
||||||
var roomSubtitle: String?
|
var roomSubtitle: String?
|
||||||
let roomAvatar: RoomAvatar
|
let roomAvatar: RoomAvatar
|
||||||
var dmRecipientVerificationState: UserIdentityVerificationState?
|
var dmRecipientDetails = DMRecipientDetails()
|
||||||
var roomHistorySharingState: RoomHistorySharingState?
|
var roomHistorySharingState: RoomHistorySharingState?
|
||||||
|
|
||||||
let mediaProvider: MediaProviderProtocol?
|
let mediaProvider: MediaProviderProtocol?
|
||||||
@@ -49,10 +54,20 @@ struct RoomHeaderView: View {
|
|||||||
|
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(roomName)
|
HStack(spacing: 8) {
|
||||||
.lineLimit(1)
|
Text(roomName)
|
||||||
.font(.compound.bodyMDSemibold)
|
.lineLimit(1)
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
|
.font(.compound.bodyMDSemibold)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
|
||||||
|
|
||||||
|
if let statusEmoji = dmRecipientDetails.status?.displayed?.emoji {
|
||||||
|
Text(String(statusEmoji))
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let roomSubtitle {
|
if let roomSubtitle {
|
||||||
Text(roomSubtitle)
|
Text(roomSubtitle)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@@ -61,8 +76,8 @@ struct RoomHeaderView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let dmRecipientVerificationState {
|
if let verificationState = dmRecipientDetails.verification {
|
||||||
VerificationBadge(verificationState: dmRecipientVerificationState, size: .xSmall, relativeTo: .compound.bodyMDSemibold)
|
VerificationBadge(verificationState: verificationState, size: .xSmall, relativeTo: .compound.bodyMDSemibold)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let historySharingIcon {
|
if let historySharingIcon {
|
||||||
@@ -112,35 +127,61 @@ private extension View {
|
|||||||
|
|
||||||
struct RoomHeaderView_Previews: PreviewProvider, TestablePreview {
|
struct RoomHeaderView_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 32) {
|
||||||
makeHeader(avatarURL: nil, verificationState: .notVerified)
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .notVerified)
|
makeHeader(avatarURL: nil)
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verified)
|
makeHeader(avatarURL: .mockMXCAvatar)
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verificationViolation)
|
|
||||||
makeHeader(avatarURL: .mockMXCAvatar,
|
makeHeader(avatarURL: .mockMXCAvatar, historySharingState: .shared)
|
||||||
roomSubtitle: "Subtitle",
|
makeHeader(avatarURL: .mockMXCAvatar, historySharingState: .worldReadable)
|
||||||
verificationState: .verified)
|
}
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .notVerified, historySharingState: .shared)
|
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .notVerified, historySharingState: .worldReadable)
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verified, historySharingState: .shared)
|
makeHeader(avatarURL: .mockMXCUserAvatar, verificationState: .verified)
|
||||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verificationViolation, historySharingState: .worldReadable)
|
makeHeader(avatarURL: .mockMXCUserAvatar, verificationState: .verificationViolation)
|
||||||
|
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
userStatus: .mockHoliday,
|
||||||
|
verificationState: .notVerified)
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
userStatus: .mockCall,
|
||||||
|
verificationState: .verificationViolation)
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
roomSubtitle: "Subtitle",
|
||||||
|
userStatus: .mockFocussing,
|
||||||
|
verificationState: .verified)
|
||||||
|
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
roomSubtitle: "Subtitle",
|
||||||
|
verificationState: .verified)
|
||||||
|
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
userStatus: .mockHoliday,
|
||||||
|
verificationState: .verified,
|
||||||
|
historySharingState: .shared)
|
||||||
|
makeHeader(avatarURL: .mockMXCUserAvatar,
|
||||||
|
verificationState: .verificationViolation,
|
||||||
|
historySharingState: .worldReadable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.previewLayout(.sizeThatFits)
|
.previewLayout(.sizeThatFits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
static func makeHeader(avatarURL: URL?,
|
static func makeHeader(avatarURL: URL?,
|
||||||
roomSubtitle: String? = nil,
|
roomSubtitle: String? = nil,
|
||||||
verificationState: UserIdentityVerificationState,
|
userStatus: UserStatus? = nil,
|
||||||
|
verificationState: UserIdentityVerificationState? = nil,
|
||||||
historySharingState: RoomHistorySharingState? = nil) -> some View {
|
historySharingState: RoomHistorySharingState? = nil) -> some View {
|
||||||
RoomHeaderView(roomName: "Some Room name",
|
let roomName = verificationState == nil ? "Some Room Name" : "Some User Name"
|
||||||
|
RoomHeaderView(roomName: roomName,
|
||||||
roomSubtitle: roomSubtitle,
|
roomSubtitle: roomSubtitle,
|
||||||
roomAvatar: .room(id: "1",
|
roomAvatar: .room(id: "1",
|
||||||
name: "Some Room Name",
|
name: roomName,
|
||||||
avatarURL: avatarURL),
|
avatarURL: avatarURL),
|
||||||
dmRecipientVerificationState: verificationState,
|
dmRecipientDetails: .init(status: userStatus, verification: verificationState),
|
||||||
roomHistorySharingState: historySharingState,
|
roomHistorySharingState: historySharingState,
|
||||||
|
|
||||||
mediaProvider: MediaProviderMock(.init())) { }
|
mediaProvider: MediaProviderMock(.init())) { }
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ enum RoomScreenViewAction {
|
|||||||
struct RoomScreenViewState: BindableState {
|
struct RoomScreenViewState: BindableState {
|
||||||
var roomTitle = ""
|
var roomTitle = ""
|
||||||
var roomAvatar: RoomAvatar
|
var roomAvatar: RoomAvatar
|
||||||
var dmRecipientVerificationState: UserIdentityVerificationState?
|
var dmRecipientDetails = RoomHeaderView.DMRecipientDetails()
|
||||||
|
|
||||||
var lastScrollDirection: ScrollDirection?
|
var lastScrollDirection: ScrollDirection?
|
||||||
// This is used to control the banner
|
// This is used to control the banner
|
||||||
|
|||||||
@@ -274,17 +274,17 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
guard roomProxy.infoPublisher.value.isDM,
|
guard roomProxy.infoPublisher.value.isDM,
|
||||||
let dmRecipient = roomProxy.membersPublisher.value.first(where: { $0.userID != roomProxy.ownUserID }),
|
let dmRecipient = roomProxy.membersPublisher.value.first(where: { $0.userID != roomProxy.ownUserID }),
|
||||||
case let .success(userIdentity) = await clientProxy.userIdentity(for: dmRecipient.userID, fallBackToServer: true) else {
|
case let .success(userIdentity) = await clientProxy.userIdentity(for: dmRecipient.userID, fallBackToServer: true) else {
|
||||||
state.dmRecipientVerificationState = .notVerified
|
state.dmRecipientDetails.verification = .notVerified
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let userIdentity else {
|
guard let userIdentity else {
|
||||||
MXLog.failure("User identity should be known at this point")
|
MXLog.failure("User identity should be known at this point")
|
||||||
state.dmRecipientVerificationState = .notVerified
|
state.dmRecipientDetails.verification = .notVerified
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state.dmRecipientVerificationState = userIdentity.verificationState
|
state.dmRecipientDetails.verification = userIdentity.verificationState
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resolveIdentityPinningViolation(_ userID: String) async {
|
private func resolveIdentityPinningViolation(_ userID: String) async {
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ struct RoomScreen: View {
|
|||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
RoomHeaderView(roomName: context.viewState.roomTitle,
|
RoomHeaderView(roomName: context.viewState.roomTitle,
|
||||||
roomAvatar: context.viewState.roomAvatar,
|
roomAvatar: context.viewState.roomAvatar,
|
||||||
dmRecipientVerificationState: context.viewState.dmRecipientVerificationState,
|
dmRecipientDetails: context.viewState.dmRecipientDetails,
|
||||||
roomHistorySharingState: context.viewState.roomHistorySharingState,
|
roomHistorySharingState: context.viewState.roomHistorySharingState,
|
||||||
mediaProvider: context.mediaProvider) {
|
mediaProvider: context.mediaProvider) {
|
||||||
context.send(viewAction: .displayRoomDetails)
|
context.send(viewAction: .displayRoomDetails)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct ThreadTimelineScreenViewState: BindableState {
|
|||||||
var roomTitle: String
|
var roomTitle: String
|
||||||
var roomAvatar: RoomAvatar
|
var roomAvatar: RoomAvatar
|
||||||
var canSendMessage = true
|
var canSendMessage = true
|
||||||
var dmRecipientVerificationState: UserIdentityVerificationState?
|
var dmRecipientDetails = RoomHeaderView.DMRecipientDetails()
|
||||||
var roomHistorySharingState: RoomHistorySharingState?
|
var roomHistorySharingState: RoomHistorySharingState?
|
||||||
|
|
||||||
var bindings = ThreadTimelineScreenViewStateBindings()
|
var bindings = ThreadTimelineScreenViewStateBindings()
|
||||||
|
|||||||
@@ -87,17 +87,17 @@ class ThreadTimelineScreenViewModel: ThreadTimelineScreenViewModelType, ThreadTi
|
|||||||
guard roomProxy.infoPublisher.value.isDM,
|
guard roomProxy.infoPublisher.value.isDM,
|
||||||
let dmRecipient = roomProxy.membersPublisher.value.first(where: { $0.userID != roomProxy.ownUserID }),
|
let dmRecipient = roomProxy.membersPublisher.value.first(where: { $0.userID != roomProxy.ownUserID }),
|
||||||
case let .success(userIdentity) = await userSession.clientProxy.userIdentity(for: dmRecipient.userID, fallBackToServer: true) else {
|
case let .success(userIdentity) = await userSession.clientProxy.userIdentity(for: dmRecipient.userID, fallBackToServer: true) else {
|
||||||
state.dmRecipientVerificationState = .notVerified
|
state.dmRecipientDetails.verification = .notVerified
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let userIdentity else {
|
guard let userIdentity else {
|
||||||
MXLog.failure("User identity should be known at this point")
|
MXLog.failure("User identity should be known at this point")
|
||||||
state.dmRecipientVerificationState = .notVerified
|
state.dmRecipientDetails.verification = .notVerified
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state.dmRecipientVerificationState = userIdentity.verificationState
|
state.dmRecipientDetails.verification = userIdentity.verificationState
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateRoomInfo(_ roomInfo: RoomInfoProxyProtocol) {
|
private func updateRoomInfo(_ roomInfo: RoomInfoProxyProtocol) {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ struct ThreadTimelineScreen: View {
|
|||||||
RoomHeaderView(roomName: L10n.commonThread,
|
RoomHeaderView(roomName: L10n.commonThread,
|
||||||
roomSubtitle: context.viewState.roomTitle,
|
roomSubtitle: context.viewState.roomTitle,
|
||||||
roomAvatar: context.viewState.roomAvatar,
|
roomAvatar: context.viewState.roomAvatar,
|
||||||
dmRecipientVerificationState: context.viewState.dmRecipientVerificationState,
|
dmRecipientDetails: context.viewState.dmRecipientDetails,
|
||||||
roomHistorySharingState: context.viewState.roomHistorySharingState,
|
roomHistorySharingState: context.viewState.roomHistorySharingState,
|
||||||
mediaProvider: context.mediaProvider) {
|
mediaProvider: context.mediaProvider) {
|
||||||
// There is no action but the iOS 26 designs have it looking like a button.
|
// There is no action but the iOS 26 designs have it looking like a button.
|
||||||
|
|||||||
Reference in New Issue
Block a user