mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Show or create direct message room (#716)
* add start chat flow with UI * add feature flag for start chat * add changelog * fix naming and tests * fix empty display name in user cell * Update ElementX/Sources/Application/AppSettings.swift Co-authored-by: Alfonso Grillo <alfogrillo@element.io> * add screenshots from UI test * fix swiftFormat and add identifiers * fix warnings * add the create or open already existing direct room feature * add changelog * add UserProfile object, add loading indicator in start chat flow * fix test * add new section for user profile in start chat * fix duplicates input on search bar * Update ElementX/Sources/Services/Client/ClientProxy.swift Co-authored-by: Alfonso Grillo <alfogrillo@element.io> * Update ElementX/Sources/Services/Client/ClientProxy.swift Co-authored-by: Alfonso Grillo <alfogrillo@element.io> --------- Co-authored-by: Alfonso Grillo <alfogrillo@element.io>
This commit is contained in:
@@ -139,3 +139,5 @@
|
|||||||
"verification_compare_emojis_title" = "Compare emojis";
|
"verification_compare_emojis_title" = "Compare emojis";
|
||||||
"verification_compare_emojis_detail" = "Confirm that the emojis below match those shown on your other session.";
|
"verification_compare_emojis_detail" = "Confirm that the emojis below match those shown on your other session.";
|
||||||
"verification_conclusion_ok_self_notice_title" = "Verification complete";
|
"verification_conclusion_ok_self_notice_title" = "Verification complete";
|
||||||
|
|
||||||
|
"retrieving_direct_room_error" = "An error occurred when trying to start a chat";
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ extension ElementL10n {
|
|||||||
public static let reportContentInfo = ElementL10n.tr("Untranslated", "report_content_info")
|
public static let reportContentInfo = ElementL10n.tr("Untranslated", "report_content_info")
|
||||||
/// Report Submitted
|
/// Report Submitted
|
||||||
public static let reportContentSubmitted = ElementL10n.tr("Untranslated", "report_content_submitted")
|
public static let reportContentSubmitted = ElementL10n.tr("Untranslated", "report_content_submitted")
|
||||||
|
/// An error occurred when trying to start a chat
|
||||||
|
public static let retrievingDirectRoomError = ElementL10n.tr("Untranslated", "retrieving_direct_room_error")
|
||||||
/// About
|
/// About
|
||||||
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
|
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
|
||||||
/// Copy Link
|
/// Copy Link
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension UserProfileProxy {
|
||||||
|
// Mocks
|
||||||
|
static var mockAlice: UserProfileProxy {
|
||||||
|
.init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: URL(staticString: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA"))
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockBob: UserProfileProxy {
|
||||||
|
.init(userID: "@bob:matrix.org", displayName: "Bob", avatarURL: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockCharlie: UserProfileProxy {
|
||||||
|
.init(userID: "@charlie:matrix.org", displayName: "Charlie", avatarURL: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ enum UserAvatarSizeOnScreen {
|
|||||||
case home
|
case home
|
||||||
case settings
|
case settings
|
||||||
case roomDetails
|
case roomDetails
|
||||||
|
case startChat
|
||||||
|
|
||||||
var value: CGFloat {
|
var value: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -57,6 +58,8 @@ enum UserAvatarSizeOnScreen {
|
|||||||
return 60
|
return 60
|
||||||
case .roomDetails:
|
case .roomDetails:
|
||||||
return 44
|
return 44
|
||||||
|
case .startChat:
|
||||||
|
return 36
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import SwiftUI
|
|||||||
|
|
||||||
struct StartChatCoordinatorParameters {
|
struct StartChatCoordinatorParameters {
|
||||||
let userSession: UserSessionProtocol
|
let userSession: UserSessionProtocol
|
||||||
|
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StartChatCoordinatorAction {
|
enum StartChatCoordinatorAction {
|
||||||
case close
|
case close
|
||||||
|
case openRoom(withIdentifier: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class StartChatCoordinator: CoordinatorProtocol {
|
final class StartChatCoordinator: CoordinatorProtocol {
|
||||||
@@ -33,7 +35,7 @@ final class StartChatCoordinator: CoordinatorProtocol {
|
|||||||
init(parameters: StartChatCoordinatorParameters) {
|
init(parameters: StartChatCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = StartChatViewModel(userSession: parameters.userSession)
|
viewModel = StartChatViewModel(userSession: parameters.userSession, userIndicatorController: parameters.userIndicatorController)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -43,8 +45,9 @@ final class StartChatCoordinator: CoordinatorProtocol {
|
|||||||
case .close:
|
case .close:
|
||||||
self.callback?(.close)
|
self.callback?(.close)
|
||||||
case .createRoom:
|
case .createRoom:
|
||||||
// TODO: start create room flow
|
|
||||||
break
|
break
|
||||||
|
case .openRoom(let identifier):
|
||||||
|
self.callback?(.openRoom(withIdentifier: identifier))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,48 @@ import Foundation
|
|||||||
enum StartChatViewModelAction {
|
enum StartChatViewModelAction {
|
||||||
case close
|
case close
|
||||||
case createRoom
|
case createRoom
|
||||||
|
case openRoom(withIdentifier: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StartChatViewState: BindableState {
|
struct StartChatViewState: BindableState {
|
||||||
var bindings = StartChatScreenViewStateBindings()
|
var bindings = StartChatScreenViewStateBindings()
|
||||||
|
|
||||||
// TODO: bind with real service, and mock data only in preview
|
var isSearching: Bool {
|
||||||
var suggestedUsers: [RoomMemberProxyMock] = [.mockAlice, .mockBob, .mockCharlie]
|
!bindings.searchQuery.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var usersSection: StartChatUsersSection = .init(type: .suggestions, users: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StartChatUserSectionType {
|
||||||
|
case searchResult
|
||||||
|
case suggestions
|
||||||
|
|
||||||
|
var title: String? {
|
||||||
|
switch self {
|
||||||
|
case .searchResult:
|
||||||
|
return nil
|
||||||
|
case .suggestions:
|
||||||
|
return ElementL10n.directRoomUserListSuggestionsTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StartChatUsersSection {
|
||||||
|
var type: StartChatUserSectionType
|
||||||
|
var users: [UserProfileProxy]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StartChatScreenViewStateBindings {
|
struct StartChatScreenViewStateBindings {
|
||||||
var searchQuery = ""
|
var searchQuery = ""
|
||||||
|
|
||||||
|
/// Information describing the currently displayed alert.
|
||||||
|
var alertInfo: AlertInfo<ClientProxyError>?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StartChatViewAction {
|
enum StartChatViewAction {
|
||||||
case close
|
case close
|
||||||
case createRoom
|
case createRoom
|
||||||
case inviteFriends
|
case inviteFriends
|
||||||
|
case selectUser(UserProfileProxy)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias StartChatViewModelType = StateStoreViewModel<StartChatViewState, StartChatViewAction>
|
typealias StartChatViewModelType = StateStoreViewModel<StartChatViewState, StartChatViewAction>
|
||||||
@@ -22,10 +23,15 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
|
|||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
|
|
||||||
var callback: ((StartChatViewModelAction) -> Void)?
|
var callback: ((StartChatViewModelAction) -> Void)?
|
||||||
|
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol) {
|
init(userSession: UserSessionProtocol, userIndicatorController: UserIndicatorControllerProtocol?) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
|
self.userIndicatorController = userIndicatorController
|
||||||
super.init(initialViewState: StartChatViewState(), imageProvider: userSession.mediaProvider)
|
super.init(initialViewState: StartChatViewState(), imageProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
|
setupBindings()
|
||||||
|
fetchSuggestion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -37,8 +43,90 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
|
|||||||
case .createRoom:
|
case .createRoom:
|
||||||
callback?(.createRoom)
|
callback?(.createRoom)
|
||||||
case .inviteFriends:
|
case .inviteFriends:
|
||||||
// TODO: start invite people flow
|
|
||||||
break
|
break
|
||||||
|
case .selectUser(let user):
|
||||||
|
showLoadingIndicator()
|
||||||
|
Task {
|
||||||
|
let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(user.userID)
|
||||||
|
switch currentDirectRoom {
|
||||||
|
case .success(.some(let roomId)):
|
||||||
|
self.hideLoadingIndicator()
|
||||||
|
self.callback?(.openRoom(withIdentifier: roomId))
|
||||||
|
case .success(nil):
|
||||||
|
await self.createDirectRoom(with: user)
|
||||||
|
case .failure(let failure):
|
||||||
|
self.hideLoadingIndicator()
|
||||||
|
self.displayError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayError(_ type: ClientProxyError) {
|
||||||
|
switch type {
|
||||||
|
case .failedRetrievingDirectRoom:
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: type,
|
||||||
|
title: ElementL10n.dialogTitleError,
|
||||||
|
message: ElementL10n.retrievingDirectRoomError)
|
||||||
|
case .failedCreatingRoom:
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: type,
|
||||||
|
title: ElementL10n.dialogTitleError,
|
||||||
|
message: ElementL10n.retrievingDirectRoomError)
|
||||||
|
default:
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupBindings() {
|
||||||
|
context.$viewState
|
||||||
|
.map(\.bindings.searchQuery)
|
||||||
|
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink { [weak self] searchQuery in
|
||||||
|
if searchQuery.isEmpty {
|
||||||
|
self?.fetchSuggestion()
|
||||||
|
} else if MatrixEntityRegex.isMatrixUserIdentifier(searchQuery) {
|
||||||
|
self?.state.usersSection.type = .searchResult
|
||||||
|
self?.state.usersSection.users = [UserProfileProxy(userID: searchQuery, displayName: nil, avatarURL: nil)]
|
||||||
|
} else {
|
||||||
|
self?.state.usersSection.type = .searchResult
|
||||||
|
self?.state.usersSection.users = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchSuggestion() {
|
||||||
|
state.usersSection.type = .suggestions
|
||||||
|
state.usersSection.users = [.mockAlice, .mockBob, .mockCharlie]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createDirectRoom(with user: UserProfileProxy) async {
|
||||||
|
showLoadingIndicator()
|
||||||
|
let result = await userSession.clientProxy.createDirectRoom(with: user.userID)
|
||||||
|
hideLoadingIndicator()
|
||||||
|
switch result {
|
||||||
|
case .success(let roomId):
|
||||||
|
callback?(.openRoom(withIdentifier: roomId))
|
||||||
|
case .failure(let failure):
|
||||||
|
displayError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Loading indicator
|
||||||
|
|
||||||
|
static let loadingIndicatorIdentifier = "StartChatLoading"
|
||||||
|
|
||||||
|
private func showLoadingIndicator() {
|
||||||
|
userIndicatorController?.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||||
|
type: .modal,
|
||||||
|
title: ElementL10n.loading,
|
||||||
|
persistent: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideLoadingIndicator() {
|
||||||
|
userIndicatorController?.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,8 @@ import Foundation
|
|||||||
protocol StartChatViewModelProtocol {
|
protocol StartChatViewModelProtocol {
|
||||||
var callback: ((StartChatViewModelAction) -> Void)? { get set }
|
var callback: ((StartChatViewModelAction) -> Void)? { get set }
|
||||||
var context: StartChatViewModelType.Context { get }
|
var context: StartChatViewModelType.Context { get }
|
||||||
|
|
||||||
|
/// Display an error to the user.
|
||||||
|
/// - Parameter type: The type of error to be displayed.
|
||||||
|
func displayError(_ type: ClientProxyError)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ struct StartChatScreen: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
createRoomSection
|
if !context.viewState.isSearching {
|
||||||
inviteFriendsSection
|
createRoomSection
|
||||||
suggestionsSection
|
inviteFriendsSection
|
||||||
|
}
|
||||||
|
usersSection
|
||||||
}
|
}
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(Color.element.formBackground.ignoresSafeArea())
|
.background(Color.element.formBackground.ignoresSafeArea())
|
||||||
@@ -35,6 +37,7 @@ struct StartChatScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: ElementL10n.searchForSomeone)
|
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: ElementL10n.searchForSomeone)
|
||||||
|
.alert(item: $context.alertInfo) { $0.alert }
|
||||||
}
|
}
|
||||||
|
|
||||||
private var createRoomSection: some View {
|
private var createRoomSection: some View {
|
||||||
@@ -58,14 +61,19 @@ struct StartChatScreen: View {
|
|||||||
.formSectionStyle()
|
.formSectionStyle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var suggestionsSection: some View {
|
private var usersSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ForEach(context.viewState.suggestedUsers, id: \.userID) { user in
|
ForEach(context.viewState.usersSection.users, id: \.userID) { user in
|
||||||
StartChatSuggestedUserCell(user: user, imageProvider: context.imageProvider)
|
Button { context.send(viewAction: .selectUser(user)) } label: {
|
||||||
|
StartChatSuggestedUserCell(user: user, imageProvider: context.imageProvider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(ElementL10n.directRoomUserListSuggestionsTitle)
|
if let title = context.viewState.usersSection.type.title {
|
||||||
|
Text(title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.listRowSeparator(.automatic)
|
||||||
.formSectionStyle()
|
.formSectionStyle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +101,7 @@ struct StartChat_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
|
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
|
||||||
mediaProvider: MockMediaProvider())
|
mediaProvider: MockMediaProvider())
|
||||||
let regularViewModel = StartChatViewModel(userSession: userSession)
|
let regularViewModel = StartChatViewModel(userSession: userSession, userIndicatorController: nil)
|
||||||
NavigationView {
|
NavigationView {
|
||||||
StartChatScreen(context: regularViewModel.context)
|
StartChatScreen(context: regularViewModel.context)
|
||||||
.tint(.element.accent)
|
.tint(.element.accent)
|
||||||
|
|||||||
@@ -17,25 +17,23 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct StartChatSuggestedUserCell: View {
|
struct StartChatSuggestedUserCell: View {
|
||||||
let user: RoomMemberProxyProtocol
|
let user: UserProfileProxy
|
||||||
let imageProvider: ImageProviderProtocol?
|
let imageProvider: ImageProviderProtocol?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 13) {
|
HStack(spacing: 16) {
|
||||||
LoadableAvatarImage(url: user.avatarURL,
|
LoadableAvatarImage(url: user.avatarURL,
|
||||||
name: user.displayName,
|
name: user.displayName,
|
||||||
contentID: user.userID,
|
contentID: user.userID,
|
||||||
avatarSize: .user(on: .home),
|
avatarSize: .user(on: .startChat),
|
||||||
imageProvider: imageProvider)
|
imageProvider: imageProvider)
|
||||||
|
.padding(.vertical, 10)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
// covers both nil and empty state
|
Text(user.displayName ?? user.userID)
|
||||||
let displayName = user.displayName ?? ""
|
|
||||||
Text(displayName.isEmpty ? user.userID : displayName)
|
|
||||||
.font(.element.title3)
|
.font(.element.title3)
|
||||||
.foregroundColor(.element.primaryContent)
|
.foregroundColor(.element.primaryContent)
|
||||||
if !displayName.isEmpty {
|
if user.displayName != nil {
|
||||||
Text(user.userID)
|
Text(user.userID)
|
||||||
.font(.element.subheadline)
|
.font(.element.subheadline)
|
||||||
.foregroundColor(.element.tertiaryContent)
|
.foregroundColor(.element.tertiaryContent)
|
||||||
|
|||||||
@@ -143,6 +143,29 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
slidingSyncObserverToken = nil
|
slidingSyncObserverToken = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||||
|
await Task.dispatch(on: clientQueue) {
|
||||||
|
do {
|
||||||
|
let roomId = try self.client.getDmRoom(userId: userID)?.id()
|
||||||
|
return .success(roomId)
|
||||||
|
} catch {
|
||||||
|
return .failure(.failedRetrievingDirectRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDirectRoom(with userID: String) async -> Result<String, ClientProxyError> {
|
||||||
|
await Task.dispatch(on: clientQueue) {
|
||||||
|
do {
|
||||||
|
let parameters = CreateRoomParameters(name: "", topic: nil, isEncrypted: true, isDirect: true, visibility: .private, preset: .trustedPrivateChat, invite: [userID], avatar: nil)
|
||||||
|
let result = try self.client.createRoom(request: parameters)
|
||||||
|
return .success(result)
|
||||||
|
} catch {
|
||||||
|
return .failure(.failedCreatingRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
||||||
let (slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) {
|
let (slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) {
|
||||||
self.roomTupleForIdentifier(identifier)
|
self.roomTupleForIdentifier(identifier)
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ enum ClientProxyCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ClientProxyError: Error {
|
enum ClientProxyError: Error {
|
||||||
|
case failedCreatingRoom
|
||||||
|
case failedRetrievingDirectRoom
|
||||||
case failedRetrievingDisplayName
|
case failedRetrievingDisplayName
|
||||||
case failedRetrievingAccountData
|
case failedRetrievingAccountData
|
||||||
case failedSettingAccountData
|
case failedSettingAccountData
|
||||||
@@ -70,6 +72,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
func stopSync()
|
func stopSync()
|
||||||
|
|
||||||
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError>
|
||||||
|
|
||||||
|
func createDirectRoom(with userID: String) async -> Result<String, ClientProxyError>
|
||||||
|
|
||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
||||||
|
|
||||||
func loadUserDisplayName() async -> Result<String, ClientProxyError>
|
func loadUserDisplayName() async -> Result<String, ClientProxyError>
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ class MockClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
func stopSync() { }
|
func stopSync() { }
|
||||||
|
|
||||||
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||||
|
.failure(.failedRetrievingDirectRoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDirectRoom(with userID: String) async -> Result<String, ClientProxyError> {
|
||||||
|
.failure(.failedCreatingRoom)
|
||||||
|
}
|
||||||
|
|
||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
||||||
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -260,13 +260,16 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
let userIndicatorController = UserIndicatorController(rootCoordinator: startChatNavigationStackCoordinator)
|
let userIndicatorController = UserIndicatorController(rootCoordinator: startChatNavigationStackCoordinator)
|
||||||
|
|
||||||
let parameters = StartChatCoordinatorParameters(userSession: userSession)
|
let parameters = StartChatCoordinatorParameters(userSession: userSession, userIndicatorController: userIndicatorController)
|
||||||
let coordinator = StartChatCoordinator(parameters: parameters)
|
let coordinator = StartChatCoordinator(parameters: parameters)
|
||||||
coordinator.callback = { [weak self] action in
|
coordinator.callback = { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .close:
|
case .close:
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .openRoom(let identifier):
|
||||||
|
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
|
self.stateMachine.processEvent(.selectRoom(roomId: identifier))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
struct UserProfileProxy {
|
||||||
|
let userID: String
|
||||||
|
let displayName: String?
|
||||||
|
let avatarURL: URL?
|
||||||
|
|
||||||
|
init(userID: String, displayName: String?, avatarURL: URL?) {
|
||||||
|
self.userID = userID
|
||||||
|
self.displayName = displayName
|
||||||
|
self.avatarURL = avatarURL
|
||||||
|
}
|
||||||
|
|
||||||
|
init(userProfile: UserProfile) {
|
||||||
|
userID = userProfile.userId
|
||||||
|
displayName = userProfile.displayName
|
||||||
|
avatarURL = userProfile.avatarUrl.flatMap(URL.init(string:))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ class StartChatScreenViewModelTests: XCTestCase {
|
|||||||
@MainActor override func setUpWithError() throws {
|
@MainActor override func setUpWithError() throws {
|
||||||
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: ""),
|
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: ""),
|
||||||
mediaProvider: MockMediaProvider())
|
mediaProvider: MockMediaProvider())
|
||||||
viewModel = StartChatViewModel(userSession: userSession)
|
viewModel = StartChatViewModel(userSession: userSession, userIndicatorController: nil)
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Show or create direct message room
|
||||||
Reference in New Issue
Block a user