mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
DM Details View (#744)
* Basic implementation completed but it requires some bug fixing and polishing (+4 squashed commits) Squashed commits: [43f0064e] now the content of the room details reflects the expected dm details content when the conditions are met [fe6f2b5e] replaced existing headers with a composable and reusable HeaderView [3c00120a] now the members are fetched every time the room is opened [421d539b] refactored the members fetching to be immediate in case the room could be a direct chat * polished the code and fixed some bugs that prevented the updated state of the members to be displayed in the room details view (+1 squashed commit) Squashed commits: [6bf98473] fix for the button not being destructive in room details screen * removed copy button and updated copy * moved a file and renamed header view * addressed all PR comments * sdk bump * UI tests * unit tests * changelog * updated mocks * removed a duplicated signature in a mock
This commit is contained in:
@@ -88,7 +88,7 @@ public enum L10n {
|
||||
public static var actionSearch: String { return L10n.tr("Localizable", "action_search") }
|
||||
/// Send
|
||||
public static var actionSend: String { return L10n.tr("Localizable", "action_send") }
|
||||
/// Share link
|
||||
/// Share
|
||||
public static var actionShareLink: String { return L10n.tr("Localizable", "action_share_link") }
|
||||
/// Skip
|
||||
public static var actionSkip: String { return L10n.tr("Localizable", "action_skip") }
|
||||
@@ -108,6 +108,8 @@ public enum L10n {
|
||||
public static var commonAudio: String { return L10n.tr("Localizable", "common_audio") }
|
||||
/// Bubbles
|
||||
public static var commonBubbles: String { return L10n.tr("Localizable", "common_bubbles") }
|
||||
/// Creating room…
|
||||
public static var commonCreatingRoom: String { return L10n.tr("Localizable", "common_creating_room") }
|
||||
/// Decryption error
|
||||
public static var commonDecryptionError: String { return L10n.tr("Localizable", "common_decryption_error") }
|
||||
/// Developer options
|
||||
@@ -230,6 +232,8 @@ public enum L10n {
|
||||
public static var errorFailedCreatingThePermalink: String { return L10n.tr("Localizable", "error_failed_creating_the_permalink") }
|
||||
/// Failed loading messages
|
||||
public static var errorFailedLoadingMessages: String { return L10n.tr("Localizable", "error_failed_loading_messages") }
|
||||
/// No compatible app was found to handle this action.
|
||||
public static var errorNoCompatibleAppFound: String { return L10n.tr("Localizable", "error_no_compatible_app_found") }
|
||||
/// Some messages have not been sent
|
||||
public static var errorSomeMessagesHaveNotBeenSent: String { return L10n.tr("Localizable", "error_some_messages_have_not_been_sent") }
|
||||
/// Sorry, an error occurred
|
||||
@@ -346,6 +350,18 @@ public enum L10n {
|
||||
public static var screenCreateRoomActionInvitePeople: String { return L10n.tr("Localizable", "screen_create_room_action_invite_people") }
|
||||
/// Add people
|
||||
public static var screenCreateRoomAddPeopleTitle: String { return L10n.tr("Localizable", "screen_create_room_add_people_title") }
|
||||
/// Block
|
||||
public static var screenDmDetailsBlockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_action") }
|
||||
/// Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.
|
||||
public static var screenDmDetailsBlockAlertDescription: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_description") }
|
||||
/// Block user
|
||||
public static var screenDmDetailsBlockUser: String { return L10n.tr("Localizable", "screen_dm_details_block_user") }
|
||||
/// Unblock
|
||||
public static var screenDmDetailsUnblockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_unblock_alert_action") }
|
||||
/// On unblocking the user, you will be able to see all messages by them again.
|
||||
public static var screenDmDetailsUnblockAlertDescription: String { return L10n.tr("Localizable", "screen_dm_details_unblock_alert_description") }
|
||||
/// Unblock user
|
||||
public static var screenDmDetailsUnblockUser: String { return L10n.tr("Localizable", "screen_dm_details_unblock_user") }
|
||||
/// This account has been deactivated.
|
||||
public static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
||||
/// Incorrect username and/or password
|
||||
@@ -388,6 +404,8 @@ public enum L10n {
|
||||
public static var screenRoomDetailsPeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_people_title") }
|
||||
/// Security
|
||||
public static var screenRoomDetailsSecurityTitle: String { return L10n.tr("Localizable", "screen_room_details_security_title") }
|
||||
/// Share room
|
||||
public static var screenRoomDetailsShareRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_share_room_title") }
|
||||
/// Topic
|
||||
public static var screenRoomDetailsTopicTitle: String { return L10n.tr("Localizable", "screen_room_details_topic_title") }
|
||||
/// Block
|
||||
@@ -402,6 +420,10 @@ public enum L10n {
|
||||
public static var screenRoomMemberDetailsUnblockAlertDescription: String { return L10n.tr("Localizable", "screen_room_member_details_unblock_alert_description") }
|
||||
/// Unblock user
|
||||
public static var screenRoomMemberDetailsUnblockUser: String { return L10n.tr("Localizable", "screen_room_member_details_unblock_user") }
|
||||
/// Plural format key: "%#@COUNT@"
|
||||
public static func screenRoomMemberListHeaderTitle(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_list_header_title", p1)
|
||||
}
|
||||
/// Create a new conversation or room
|
||||
public static var screenRoomlistA11yCreateMessage: String { return L10n.tr("Localizable", "screen_roomlist_a11y_create_message") }
|
||||
/// All Chats
|
||||
|
||||
@@ -166,6 +166,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
var displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> {
|
||||
get { return underlyingMembersPublisher }
|
||||
set(value) { underlyingMembersPublisher = value }
|
||||
}
|
||||
var underlyingMembersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never>!
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
@@ -410,23 +415,6 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return reportContentReasonReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - members
|
||||
|
||||
var membersCallsCount = 0
|
||||
var membersCalled: Bool {
|
||||
return membersCallsCount > 0
|
||||
}
|
||||
var membersReturnValue: Result<[RoomMemberProxyProtocol], RoomProxyError>!
|
||||
var membersClosure: (() async -> Result<[RoomMemberProxyProtocol], RoomProxyError>)?
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError> {
|
||||
membersCallsCount += 1
|
||||
if let membersClosure = membersClosure {
|
||||
return await membersClosure()
|
||||
} else {
|
||||
return membersReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - ignoreUser
|
||||
|
||||
var ignoreUserCallsCount = 0
|
||||
@@ -481,6 +469,18 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return leaveRoomReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - updateMembers
|
||||
|
||||
var updateMembersCallsCount = 0
|
||||
var updateMembersCalled: Bool {
|
||||
return updateMembersCallsCount > 0
|
||||
}
|
||||
var updateMembersClosure: (() async -> Void)?
|
||||
|
||||
func updateMembers() async {
|
||||
updateMembersCallsCount += 1
|
||||
await updateMembersClosure?()
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
struct RoomProxyMockConfiguration {
|
||||
@@ -51,11 +52,12 @@ extension RoomProxyMock {
|
||||
alternativeAliases = configuration.alternativeAliases
|
||||
hasUnreadNotifications = configuration.hasUnreadNotifications
|
||||
|
||||
membersClosure = {
|
||||
if let members = configuration.members {
|
||||
return .success(members)
|
||||
}
|
||||
return .failure(.failedRetrievingMembers)
|
||||
if let members = configuration.members {
|
||||
membersPublisher = Just(members).eraseToAnyPublisher()
|
||||
} else {
|
||||
membersPublisher = Just([]).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
updateMembersClosure = { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ struct A11yIdentifiers {
|
||||
|
||||
struct RoomDetailsScreen {
|
||||
let avatar = "room_details-avatar"
|
||||
let dmAvatar = "room_details-dm_avatar"
|
||||
let people = "room_details-people"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2023 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
|
||||
|
||||
struct AvatarHeaderView<Footer: View>: View {
|
||||
let avatarUrl: URL?
|
||||
let name: String?
|
||||
let id: String
|
||||
let avatarSize: AvatarSize
|
||||
let imageProvider: ImageProviderProtocol?
|
||||
let subtitle: String?
|
||||
@ViewBuilder var footer: () -> Footer
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: avatarUrl,
|
||||
name: name,
|
||||
contentID: id,
|
||||
avatarSize: avatarSize,
|
||||
imageProvider: imageProvider)
|
||||
|
||||
Text(name ?? id)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
footer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AvatarHeaderView(avatarUrl: URL.picturesDirectory,
|
||||
name: "Test",
|
||||
id: "test",
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: MockMediaProvider(),
|
||||
subtitle: "test") {
|
||||
HStack(spacing: 32) {
|
||||
ShareLink(item: "test") {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,19 +38,65 @@ struct RoomDetailsViewState: BindableState {
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
let permalink: URL?
|
||||
var members: [RoomDetailsMember]
|
||||
var members: [RoomMemberDetails] = []
|
||||
var isProcessingIgnoreRequest = false
|
||||
|
||||
var isLoadingMembers: Bool {
|
||||
members.isEmpty
|
||||
}
|
||||
|
||||
var bindings: RoomDetailsViewStateBindings
|
||||
|
||||
var dmRecipient: RoomMemberDetails?
|
||||
|
||||
private var isDMRoom: Bool {
|
||||
isEncrypted && isDirect && members.count == 2
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomDetailsViewStateBindings {
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockUser
|
||||
case .unignore: return L10n.screenDmDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenDmDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenDmDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomDetailsErrorType>?
|
||||
var leaveRoomAlertItem: LeaveRoomAlertItem?
|
||||
var ignoreUserRoomAlertItem: IgnoreUserAlertItem?
|
||||
}
|
||||
|
||||
struct LeaveRoomAlertItem: AlertItem {
|
||||
@@ -77,21 +123,11 @@ struct LeaveRoomAlertItem: AlertItem {
|
||||
enum RoomDetailsViewAction {
|
||||
case processTapPeople
|
||||
case processTapLeave
|
||||
case processTapIgnore
|
||||
case processTapUnignore
|
||||
case confirmLeave
|
||||
case copyRoomLink
|
||||
}
|
||||
|
||||
struct RoomDetailsMember: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
|
||||
@MainActor
|
||||
init(withProxy proxy: RoomMemberProxyProtocol) {
|
||||
id = proxy.userID
|
||||
name = proxy.displayName
|
||||
avatarURL = proxy.avatarURL
|
||||
}
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias RoomDetailsViewModelType = StateStoreViewModel<RoomDetailsViewState, RoomDetailsViewAction>
|
||||
@@ -22,12 +23,27 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private var members: [RoomMemberProxyProtocol] = [] {
|
||||
didSet {
|
||||
state.members = members.map { RoomDetailsMember(withProxy: $0) }
|
||||
state.members = members.map { RoomMemberDetails(withProxy: $0) }
|
||||
if roomProxy.isDirect, roomProxy.isEncrypted, members.count == 2 {
|
||||
dmRecipient = members.first(where: { !$0.isAccountOwner })
|
||||
} else {
|
||||
dmRecipient = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var dmRecipient: RoomMemberProxyProtocol? {
|
||||
didSet {
|
||||
if let dmRecipient {
|
||||
state.dmRecipient = RoomMemberDetails(withProxy: dmRecipient)
|
||||
} else {
|
||||
state.dmRecipient = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var callback: ((RoomDetailsViewModelAction) -> Void)?
|
||||
|
||||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
@@ -39,18 +55,16 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
topic: roomProxy.topic,
|
||||
avatarURL: roomProxy.avatarURL,
|
||||
permalink: roomProxy.permalink,
|
||||
members: [],
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
roomProxy.membersPublisher.sink { [weak self] members in
|
||||
self?.members = members
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Task {
|
||||
switch await roomProxy.members() {
|
||||
case .success(let members):
|
||||
self.members = members
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed retrieving room members: \(error)")
|
||||
state.bindings.alertInfo = AlertInfo(id: .alert(L10n.errorUnknown))
|
||||
}
|
||||
await roomProxy.updateMembers()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +74,6 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
switch viewAction {
|
||||
case .processTapPeople:
|
||||
callback?(.requestMemberDetailsPresentation(members))
|
||||
case .copyRoomLink:
|
||||
copyRoomLink()
|
||||
case .processTapLeave:
|
||||
guard members.count > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: .empty)
|
||||
@@ -70,21 +82,20 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||
case .confirmLeave:
|
||||
await leaveRoom()
|
||||
case .processTapIgnore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
||||
case .processTapUnignore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
||||
case .ignoreConfirmed:
|
||||
await ignore()
|
||||
case .unignoreConfirmed:
|
||||
await unignore()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||
|
||||
private func copyRoomLink() {
|
||||
if let roomLink = state.permalink {
|
||||
UIPasteboard.general.url = roomLink
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonLinkCopiedToClipboard))
|
||||
} else {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoom() async {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||
@@ -97,4 +108,28 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
|
||||
private func ignore() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await dmRecipient?.ignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.dmRecipient?.isIgnored = true
|
||||
case .failure, .none:
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
}
|
||||
}
|
||||
|
||||
private func unignore() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await dmRecipient?.unignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.dmRecipient?.isIgnored = false
|
||||
case .failure, .none:
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,27 @@ struct RoomDetailsScreen: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if let recipient = context.viewState.dmRecipient {
|
||||
dmHeaderSection(recipient: recipient)
|
||||
} else {
|
||||
normalRoomHeaderSection
|
||||
}
|
||||
|
||||
topicSection
|
||||
|
||||
aboutSection
|
||||
|
||||
|
||||
if context.viewState.dmRecipient == nil {
|
||||
aboutSection
|
||||
}
|
||||
|
||||
if context.viewState.isEncrypted {
|
||||
securitySection
|
||||
}
|
||||
|
||||
leaveRoomSection
|
||||
if let recipient = context.viewState.dmRecipient {
|
||||
ignoreUserSection(user: recipient)
|
||||
} else {
|
||||
leaveRoomSection
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
@@ -39,48 +49,53 @@ struct RoomDetailsScreen: View {
|
||||
.alert(item: $context.leaveRoomAlertItem,
|
||||
actions: leaveRoomAlertActions,
|
||||
message: leaveRoomAlertMessage)
|
||||
.alert(item: $context.ignoreUserRoomAlertItem,
|
||||
actions: blockUserAlertActions,
|
||||
message: blockUserAlertMessage)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var headerSection: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: context.viewState.avatarURL,
|
||||
name: context.viewState.title,
|
||||
contentID: context.viewState.roomId,
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: context.imageProvider)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar)
|
||||
|
||||
Text(context.viewState.title)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let canonicalAlias = context.viewState.canonicalAlias {
|
||||
Text(canonicalAlias)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var normalRoomHeaderSection: some View {
|
||||
AvatarHeaderView(avatarUrl: context.viewState.avatarURL,
|
||||
name: context.viewState.title,
|
||||
id: context.viewState.roomId,
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: context.viewState.canonicalAlias) {
|
||||
if let permalink = context.viewState.permalink {
|
||||
HStack(spacing: 32) {
|
||||
Button { context.send(viewAction: .copyRoomLink) } label: {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCopyLink))
|
||||
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionInvite))
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShareLink))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func dmHeaderSection(recipient: RoomMemberDetails) -> some View {
|
||||
AvatarHeaderView(avatarUrl: recipient.avatarURL,
|
||||
name: recipient.name,
|
||||
id: recipient.id,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: recipient.id) {
|
||||
if let permalink = recipient.permalink {
|
||||
HStack(spacing: 32) {
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShareLink))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.dmAvatar)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -158,6 +173,21 @@ struct RoomDetailsScreen: View {
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func ignoreUserSection(user: RoomMemberDetails) -> some View {
|
||||
Section {
|
||||
Button(role: user.isIgnored ? nil : .destructive) {
|
||||
context.send(viewAction: user.isIgnored ? .processTapUnignore : .processTapIgnore)
|
||||
} label: {
|
||||
Label(user.isIgnored ? L10n.screenDmDetailsUnblockUser : L10n.screenDmDetailsBlockUser,
|
||||
systemImage: "slash.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isProcessingIgnoreRequest ? .progressView : nil))
|
||||
.disabled(context.viewState.isProcessingIgnoreRequest)
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func leaveRoomAlertActions(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
@@ -169,12 +199,25 @@ struct RoomDetailsScreen: View {
|
||||
private func leaveRoomAlertMessage(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Text(item.subtitle)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func blockUserAlertActions(_ item: RoomDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle,
|
||||
role: item.action == .ignore ? .destructive : nil) {
|
||||
context.send(viewAction: item.viewAction)
|
||||
}
|
||||
}
|
||||
|
||||
private func blockUserAlertMessage(_ item: RoomDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Text(item.description)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomDetails_Previews: PreviewProvider {
|
||||
static let viewModel = {
|
||||
static let genericRoomViewModel = {
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockAlice,
|
||||
.mockBob,
|
||||
@@ -190,8 +233,28 @@ struct RoomDetails_Previews: PreviewProvider {
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
}()
|
||||
|
||||
static let dmRoomViewModel = {
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockMe,
|
||||
.mockDan
|
||||
]
|
||||
|
||||
let roomProxy = RoomProxyMock(with: .init(displayName: "DM Room",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: true,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members))
|
||||
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
RoomDetailsScreen(context: viewModel.context)
|
||||
RoomDetailsScreen(context: genericRoomViewModel.context)
|
||||
.previewDisplayName("Generic Room")
|
||||
RoomDetailsScreen(context: dmRoomViewModel.context)
|
||||
.previewDisplayName("DM Room")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,64 +19,58 @@ import Foundation
|
||||
enum RoomMemberDetailsViewModelAction { }
|
||||
|
||||
struct RoomMemberDetailsViewState: BindableState {
|
||||
let userID: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
let isAccountOwner: Bool
|
||||
let permalink: URL?
|
||||
var isIgnored: Bool
|
||||
var details: RoomMemberDetails
|
||||
var isProcessingIgnoreRequest = false
|
||||
|
||||
var bindings: RoomMemberDetailsViewStateBindings
|
||||
}
|
||||
|
||||
struct RoomMemberDetailsViewStateBindings {
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockUser
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomMemberDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ignoreUserAlert: IgnoreUserAlertItem?
|
||||
var errorAlert: ErrorAlertItem?
|
||||
}
|
||||
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockUser
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomMemberDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomMemberDetailsViewAction {
|
||||
case showUnignoreAlert
|
||||
case showIgnoreAlert
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
case copyUserLink
|
||||
}
|
||||
|
||||
@@ -25,12 +25,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
init(roomMemberProxy: RoomMemberProxyProtocol, mediaProvider: MediaProviderProtocol) {
|
||||
self.roomMemberProxy = roomMemberProxy
|
||||
let initialViewState = RoomMemberDetailsViewState(userID: roomMemberProxy.userID,
|
||||
name: roomMemberProxy.displayName,
|
||||
avatarURL: roomMemberProxy.avatarURL,
|
||||
isAccountOwner: roomMemberProxy.isAccountOwner,
|
||||
permalink: roomMemberProxy.permalink,
|
||||
isIgnored: roomMemberProxy.isIgnored,
|
||||
let initialViewState = RoomMemberDetailsViewState(details: RoomMemberDetails(withProxy: roomMemberProxy),
|
||||
bindings: .init())
|
||||
super.init(initialViewState: initialViewState, imageProvider: mediaProvider)
|
||||
}
|
||||
@@ -43,8 +38,6 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
||||
case .showIgnoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
||||
case .copyUserLink:
|
||||
copyUserLink()
|
||||
case .ignoreConfirmed:
|
||||
await ignoreUser()
|
||||
case .unignoreConfirmed:
|
||||
@@ -54,22 +47,13 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func copyUserLink() {
|
||||
if let userLink = state.permalink {
|
||||
UIPasteboard.general.url = userLink
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonLinkCopiedToClipboard))
|
||||
} else {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
|
||||
private func ignoreUser() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.ignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = true
|
||||
state.details.isIgnored = true
|
||||
case .failure:
|
||||
state.bindings.errorAlert = .init()
|
||||
}
|
||||
@@ -81,7 +65,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = false
|
||||
state.details.isIgnored = false
|
||||
case .failure:
|
||||
state.bindings.errorAlert = .init()
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct RoomMemberDetailsScreen: View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if !context.viewState.isAccountOwner {
|
||||
if !context.viewState.details.isAccountOwner {
|
||||
blockUserSection
|
||||
}
|
||||
}
|
||||
@@ -35,31 +35,16 @@ struct RoomMemberDetailsScreen: View {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@ViewBuilder
|
||||
private var headerSection: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: context.viewState.avatarURL,
|
||||
name: context.viewState.name,
|
||||
contentID: context.viewState.userID,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider)
|
||||
if let name = context.viewState.name {
|
||||
Text(name)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
Text(context.viewState.userID)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let permalink = context.viewState.permalink {
|
||||
AvatarHeaderView(avatarUrl: context.viewState.details.avatarURL,
|
||||
name: context.viewState.details.name,
|
||||
id: context.viewState.details.id,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: context.viewState.details.id) {
|
||||
if let permalink = context.viewState.details.permalink {
|
||||
HStack(spacing: 32) {
|
||||
Button { context.send(viewAction: .copyUserLink) } label: {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCopyLink))
|
||||
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
@@ -68,13 +53,11 @@ struct RoomMemberDetailsScreen: View {
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
private var blockUserSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
Button(role: context.viewState.details.isIgnored ? nil : .destructive) {
|
||||
context.send(viewAction: blockUserButtonAction)
|
||||
} label: {
|
||||
Label(blockUserButtonTitle, systemImage: "slash.circle")
|
||||
@@ -87,19 +70,19 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
|
||||
private var blockUserButtonAction: RoomMemberDetailsViewAction {
|
||||
context.viewState.isIgnored ? .showUnignoreAlert : .showIgnoreAlert
|
||||
context.viewState.details.isIgnored ? .showUnignoreAlert : .showIgnoreAlert
|
||||
}
|
||||
|
||||
private var blockUserButtonTitle: String {
|
||||
context.viewState.isIgnored ? L10n.screenRoomMemberDetailsUnblockUser : L10n.screenRoomMemberDetailsBlockUser
|
||||
context.viewState.details.isIgnored ? L10n.screenRoomMemberDetailsUnblockUser : L10n.screenRoomMemberDetailsBlockUser
|
||||
}
|
||||
|
||||
private var blockUserButtonAccessibilityIdentifier: String {
|
||||
context.viewState.isIgnored ? A11yIdentifiers.roomMemberDetailsScreen.unignore : A11yIdentifiers.roomMemberDetailsScreen.ignore
|
||||
context.viewState.details.isIgnored ? A11yIdentifiers.roomMemberDetailsScreen.unignore : A11yIdentifiers.roomMemberDetailsScreen.ignore
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func blockUserAlertActions(_ item: IgnoreUserAlertItem) -> some View {
|
||||
private func blockUserAlertActions(_ item: RoomMemberDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle,
|
||||
role: item.action == .ignore ? .destructive : nil) {
|
||||
@@ -107,7 +90,7 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func blockUserAlertMessage(_ item: IgnoreUserAlertItem) -> some View {
|
||||
private func blockUserAlertMessage(_ item: RoomMemberDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Text(item.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ enum RoomMembersListViewModelAction {
|
||||
}
|
||||
|
||||
struct RoomMembersListViewState: BindableState {
|
||||
var members: [RoomDetailsMember]
|
||||
var members: [RoomMemberDetails]
|
||||
|
||||
var bindings: RoomMembersListViewStateBindings
|
||||
|
||||
var visibleMembers: [RoomDetailsMember] {
|
||||
var visibleMembers: [RoomMemberDetails] {
|
||||
if bindings.searchQuery.isEmpty {
|
||||
return members
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class RoomMembersListViewModel: RoomMembersListViewModelType, RoomMembersListVie
|
||||
members: [RoomMemberProxyProtocol]) {
|
||||
self.mediaProvider = mediaProvider
|
||||
self.members = members
|
||||
super.init(initialViewState: .init(members: members.map { RoomDetailsMember(withProxy: $0) },
|
||||
super.init(initialViewState: .init(members: members.map { RoomMemberDetails(withProxy: $0) },
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import SwiftUI
|
||||
struct RoomMembersListMemberCell: View {
|
||||
@ScaledMetric private var avatarSize = AvatarSize.user(on: .roomDetails).value
|
||||
|
||||
let member: RoomDetailsMember
|
||||
let member: RoomMemberDetails
|
||||
let context: RoomMembersListViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -34,6 +34,11 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private(set) var displayName: String?
|
||||
|
||||
private var timelineObservationToken: TaskHandle?
|
||||
|
||||
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> {
|
||||
membersSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(slidingSyncRoom: SlidingSyncRoomProtocol,
|
||||
room: RoomProtocol,
|
||||
@@ -140,6 +145,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
timelineObservationToken = result.taskHandle
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
return .success(result.items)
|
||||
} else {
|
||||
@@ -274,18 +280,16 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError> {
|
||||
|
||||
func updateMembers() async {
|
||||
do {
|
||||
let members = try await Task.dispatch(on: .global()) {
|
||||
let members = try self.room.members()
|
||||
return members
|
||||
let roomMembers = try await Task.dispatch(on: .global()) {
|
||||
try self.room.members()
|
||||
}
|
||||
|
||||
let proxiedMembers = buildRoomMemberProxies(members: members)
|
||||
return .success(proxiedMembers)
|
||||
membersSubject.value = buildRoomMemberProxies(members: roomMembers)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingMembers)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,9 @@ protocol RoomProxyProtocol {
|
||||
var topic: String? { get }
|
||||
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> { get }
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||
@@ -79,13 +81,13 @@ protocol RoomProxyProtocol {
|
||||
|
||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError>
|
||||
|
||||
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError>
|
||||
|
||||
func updateMembers() async
|
||||
}
|
||||
|
||||
extension RoomProxyProtocol {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2023 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 Foundation
|
||||
|
||||
struct RoomMemberDetails: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
let permalink: URL?
|
||||
let isAccountOwner: Bool
|
||||
var isIgnored: Bool
|
||||
|
||||
@MainActor
|
||||
init(withProxy proxy: RoomMemberProxyProtocol) {
|
||||
id = proxy.userID
|
||||
name = proxy.displayName
|
||||
avatarURL = proxy.avatarURL
|
||||
permalink = proxy.permalink
|
||||
isAccountOwner = proxy.isAccountOwner
|
||||
isIgnored = proxy.isIgnored
|
||||
}
|
||||
}
|
||||
@@ -337,6 +337,20 @@ class MockScreen: Identifiable {
|
||||
let coordinator = RoomMemberDetailsCoordinator(parameters: .init(roomMemberProxy: RoomMemberProxyMock.mockIgnored, mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomDetailsScreenDmDetails:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let members: [RoomMemberProxyMock] = [.mockMe, .mockDan]
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
topic: "test",
|
||||
isDirect: true,
|
||||
isEncrypted: true,
|
||||
members: members))
|
||||
let coordinator = RoomDetailsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case userSessionScreen
|
||||
case roomDetailsScreen
|
||||
case roomDetailsScreenWithRoomAvatar
|
||||
case roomDetailsScreenDmDetails
|
||||
case roomMembersListScreen
|
||||
case roomMemberDetailsAccountOwner
|
||||
case roomMemberDetails
|
||||
|
||||
Reference in New Issue
Block a user