From 7e9a4cb85652eaf31d29e0456c2520ab20946bf9 Mon Sep 17 00:00:00 2001 From: Element CI Date: Mon, 3 Feb 2025 15:51:12 +0100 Subject: [PATCH 1/3] added an alert before creating a new DM --- .../en-US.lproj/Localizable.strings | 3 + .../en.lproj/Localizable.strings | 3 + ElementX/Sources/Generated/Strings.swift | 8 +++ .../Mocks/Generated/GeneratedMocks.swift | 70 ------------------- .../RoomMemberDetailsScreenModels.swift | 1 + .../RoomMemberDetailsScreenViewModel.swift | 33 +++++++-- .../UserProfileScreenModels.swift | 1 + .../UserProfileScreenViewModel.swift | 31 ++++++-- .../Sources/Services/Client/ClientProxy.swift | 17 ----- .../Services/Client/ClientProxyProtocol.swift | 2 - 10 files changed, 71 insertions(+), 98 deletions(-) diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index 0432906f0e..0749d6e3c6 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -840,6 +840,9 @@ "screen_room_error_failed_retrieving_user_details" = "Could not retrieve user details"; "screen_room_invite_again_alert_message" = "Would you like to invite them back?"; "screen_room_invite_again_alert_title" = "You are alone in this chat"; +"screen_room_member_details_alert_create_dm_confirmation_title" = "Send invite"; +"screen_room_member_details_alert_create_dm_message" = "Would you like to start a chat with %1$@?"; +"screen_room_member_details_alert_create_dm_title" = "Send invite?"; "screen_room_member_details_block_alert_action" = "Block"; "screen_room_member_details_block_alert_description" = "Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime."; "screen_room_member_details_block_user" = "Block user"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 1eabe0a8e1..e9075c402b 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -840,6 +840,9 @@ "screen_room_error_failed_retrieving_user_details" = "Could not retrieve user details"; "screen_room_invite_again_alert_message" = "Would you like to invite them back?"; "screen_room_invite_again_alert_title" = "You are alone in this chat"; +"screen_room_member_details_alert_create_dm_confirmation_title" = "Send invite"; +"screen_room_member_details_alert_create_dm_message" = "Would you like to start a chat with %1$@?"; +"screen_room_member_details_alert_create_dm_title" = "Send invite?"; "screen_room_member_details_block_alert_action" = "Block"; "screen_room_member_details_block_alert_description" = "Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime."; "screen_room_member_details_block_user" = "Block user"; diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index bbe63eb651..c728c730f0 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -1938,6 +1938,14 @@ internal enum L10n { internal static var screenRoomInviteAgainAlertMessage: String { return L10n.tr("Localizable", "screen_room_invite_again_alert_message") } /// You are alone in this chat internal static var screenRoomInviteAgainAlertTitle: String { return L10n.tr("Localizable", "screen_room_invite_again_alert_title") } + /// Send invite + internal static var screenRoomMemberDetailsAlertCreateDmConfirmationTitle: String { return L10n.tr("Localizable", "screen_room_member_details_alert_create_dm_confirmation_title") } + /// Would you like to start a chat with %1$@? + internal static func screenRoomMemberDetailsAlertCreateDmMessage(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_room_member_details_alert_create_dm_message", String(describing: p1)) + } + /// Send invite? + internal static var screenRoomMemberDetailsAlertCreateDmTitle: String { return L10n.tr("Localizable", "screen_room_member_details_alert_create_dm_title") } /// Block internal static var screenRoomMemberDetailsBlockAlertAction: String { return L10n.tr("Localizable", "screen_room_member_details_block_alert_action") } /// Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime. diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 7274e1f891..90e9e2e29b 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2455,76 +2455,6 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return accountURLActionReturnValue } } - //MARK: - createDirectRoomIfNeeded - - var createDirectRoomIfNeededWithExpectedRoomNameUnderlyingCallsCount = 0 - var createDirectRoomIfNeededWithExpectedRoomNameCallsCount: Int { - get { - if Thread.isMainThread { - return createDirectRoomIfNeededWithExpectedRoomNameUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = createDirectRoomIfNeededWithExpectedRoomNameUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - createDirectRoomIfNeededWithExpectedRoomNameUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - createDirectRoomIfNeededWithExpectedRoomNameUnderlyingCallsCount = newValue - } - } - } - } - var createDirectRoomIfNeededWithExpectedRoomNameCalled: Bool { - return createDirectRoomIfNeededWithExpectedRoomNameCallsCount > 0 - } - var createDirectRoomIfNeededWithExpectedRoomNameReceivedArguments: (userID: String, expectedRoomName: String?)? - var createDirectRoomIfNeededWithExpectedRoomNameReceivedInvocations: [(userID: String, expectedRoomName: String?)] = [] - - var createDirectRoomIfNeededWithExpectedRoomNameUnderlyingReturnValue: Result<(roomID: String, isNewRoom: Bool), ClientProxyError>! - var createDirectRoomIfNeededWithExpectedRoomNameReturnValue: Result<(roomID: String, isNewRoom: Bool), ClientProxyError>! { - get { - if Thread.isMainThread { - return createDirectRoomIfNeededWithExpectedRoomNameUnderlyingReturnValue - } else { - var returnValue: Result<(roomID: String, isNewRoom: Bool), ClientProxyError>? = nil - DispatchQueue.main.sync { - returnValue = createDirectRoomIfNeededWithExpectedRoomNameUnderlyingReturnValue - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - createDirectRoomIfNeededWithExpectedRoomNameUnderlyingReturnValue = newValue - } else { - DispatchQueue.main.sync { - createDirectRoomIfNeededWithExpectedRoomNameUnderlyingReturnValue = newValue - } - } - } - } - var createDirectRoomIfNeededWithExpectedRoomNameClosure: ((String, String?) async -> Result<(roomID: String, isNewRoom: Bool), ClientProxyError>)? - - func createDirectRoomIfNeeded(with userID: String, expectedRoomName: String?) async -> Result<(roomID: String, isNewRoom: Bool), ClientProxyError> { - createDirectRoomIfNeededWithExpectedRoomNameCallsCount += 1 - createDirectRoomIfNeededWithExpectedRoomNameReceivedArguments = (userID: userID, expectedRoomName: expectedRoomName) - DispatchQueue.main.async { - self.createDirectRoomIfNeededWithExpectedRoomNameReceivedInvocations.append((userID: userID, expectedRoomName: expectedRoomName)) - } - if let createDirectRoomIfNeededWithExpectedRoomNameClosure = createDirectRoomIfNeededWithExpectedRoomNameClosure { - return await createDirectRoomIfNeededWithExpectedRoomNameClosure(userID, expectedRoomName) - } else { - return createDirectRoomIfNeededWithExpectedRoomNameReturnValue - } - } //MARK: - directRoomForUserID var directRoomForUserIDUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift index 4fa8753cf8..cc18f87ef7 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift @@ -90,5 +90,6 @@ enum RoomMemberDetailsScreenViewAction { enum RoomMemberDetailsScreenError: Hashable { case failedOpeningDirectChat + case createDirectChatConfirmation case unknown } diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 0d95d00117..9deae1aa75 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -181,11 +181,36 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro persistent: true)) defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) } - switch await clientProxy.createDirectRoomIfNeeded(with: roomMemberProxy.userID, expectedRoomName: roomMemberProxy.displayName) { - case .success((let roomID, let isNewRoom)): - if isNewRoom { - analytics.trackCreatedRoom(isDM: true) + switch await clientProxy.directRoomForUserID(roomMemberProxy.userID) { + case .success(let roomID): + if let roomID = roomID { + actionsSubject.send(.openDirectChat(roomID: roomID)) + } else { + let string = roomMemberProxy.displayName ?? roomMemberProxy.userID + state.bindings.alertInfo = .init(id: .createDirectChatConfirmation, + title: L10n.screenRoomMemberDetailsAlertCreateDmTitle, + message: L10n.screenRoomMemberDetailsAlertCreateDmMessage(string), + primaryButton: .init(title: L10n.screenRoomMemberDetailsAlertCreateDmConfirmationTitle) { [weak self] in Task { await self?.createDirectChat() }}, + secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } + case .failure: + state.bindings.alertInfo = .init(id: .failedOpeningDirectChat) + } + } + + private func createDirectChat() async { + guard let roomMemberProxy else { fatalError() } + + let loadingIndicatorIdentifier = "createDirectChatLoadingIndicator" + userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorIdentifier, + type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), + title: L10n.commonLoading, + persistent: true)) + defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) } + + switch await clientProxy.createDirectRoom(with: roomMemberProxy.userID, expectedRoomName: roomMemberProxy.displayName) { + case .success(let roomID): + analytics.trackCreatedRoom(isDM: true) actionsSubject.send(.openDirectChat(roomID: roomID)) case .failure: state.bindings.alertInfo = .init(id: .failedOpeningDirectChat) diff --git a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift index 6c0eb27c25..9158d65983 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift @@ -51,4 +51,5 @@ enum UserProfileScreenViewAction { enum UserProfileScreenError: Hashable { case failedOpeningDirectChat case unknown + case createDirectChatConfirmation } diff --git a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift index 5d47a9c40f..bac0dc7860 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift @@ -115,12 +115,33 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr showLoadingIndicator(allowsInteraction: false) defer { hideLoadingIndicator() } - - switch await clientProxy.createDirectRoomIfNeeded(with: userProfile.userID, expectedRoomName: userProfile.displayName) { - case .success((let roomID, let isNewRoom)): - if isNewRoom { - analytics.trackCreatedRoom(isDM: true) + + switch await clientProxy.directRoomForUserID(userProfile.userID) { + case .success(let roomID): + if let roomID = roomID { + actionsSubject.send(.openDirectChat(roomID: roomID)) + } else { + let string = userProfile.displayName ?? userProfile.userID + state.bindings.alertInfo = .init(id: .createDirectChatConfirmation, + title: L10n.screenRoomMemberDetailsAlertCreateDmTitle, + message: L10n.screenRoomMemberDetailsAlertCreateDmMessage(string), + primaryButton: .init(title: L10n.screenRoomMemberDetailsAlertCreateDmConfirmationTitle) { [weak self] in Task { await self?.createDirectChat() }}, + secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } + case .failure: + state.bindings.alertInfo = .init(id: .failedOpeningDirectChat) + } + } + + private func createDirectChat() async { + guard let userProfile = state.userProfile else { fatalError() } + + showLoadingIndicator(allowsInteraction: false) + defer { hideLoadingIndicator() } + + switch await clientProxy.createDirectRoom(with: userProfile.userID, expectedRoomName: userProfile.displayName) { + case .success(let roomID): + analytics.trackCreatedRoom(isDM: true) actionsSubject.send(.openDirectChat(roomID: roomID)) case .failure: state.bindings.alertInfo = .init(id: .failedOpeningDirectChat) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index e31e0bdfc7..23931e1cd9 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -341,23 +341,6 @@ class ClientProxy: ClientProxyProtocol { try? await client.accountUrl(action: action).flatMap(URL.init(string:)) } - func createDirectRoomIfNeeded(with userID: String, expectedRoomName: String?) async -> Result<(roomID: String, isNewRoom: Bool), ClientProxyError> { - let currentDirectRoom = await directRoomForUserID(userID) - switch currentDirectRoom { - case .success(.some(let roomID)): - return .success((roomID: roomID, isNewRoom: false)) - case .success(.none): - switch await createDirectRoom(with: userID, expectedRoomName: expectedRoomName) { - case .success(let roomID): - return .success((roomID: roomID, isNewRoom: true)) - case .failure(let error): - return .failure(.sdkError(error)) - } - case .failure(let error): - return .failure(.sdkError(error)) - } - } - func directRoomForUserID(_ userID: String) async -> Result { await Task.dispatch(on: clientQueue) { do { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 68460e4e1e..7ebf534256 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -113,8 +113,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func accountURL(action: AccountManagementAction) async -> URL? - func createDirectRoomIfNeeded(with userID: String, expectedRoomName: String?) async -> Result<(roomID: String, isNewRoom: Bool), ClientProxyError> - func directRoomForUserID(_ userID: String) async -> Result func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result From 988a5dc48d3a0b37dfe6f85d2c7bab33f278ddd9 Mon Sep 17 00:00:00 2001 From: Element CI Date: Mon, 3 Feb 2025 15:57:48 +0100 Subject: [PATCH 2/3] added a small delay in the loading --- .../RoomMemberDetailsScreenViewModel.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 9deae1aa75..68d85c2e4c 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -178,7 +178,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorIdentifier, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), title: L10n.commonLoading, - persistent: true)) + persistent: true), + delay: .milliseconds(200)) defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) } switch await clientProxy.directRoomForUserID(roomMemberProxy.userID) { @@ -205,7 +206,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorIdentifier, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), title: L10n.commonLoading, - persistent: true)) + persistent: true), + delay: .milliseconds(200)) defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) } switch await clientProxy.createDirectRoom(with: roomMemberProxy.userID, expectedRoomName: roomMemberProxy.displayName) { From 3028ecae575de9ee838ae0abeb979e4dd2a04b2c Mon Sep 17 00:00:00 2001 From: Element CI Date: Mon, 3 Feb 2025 16:14:51 +0100 Subject: [PATCH 3/3] pr suggestions --- .../RoomMemberDetailsScreenModels.swift | 4 ++-- .../RoomMemberDetailsScreenViewModel.swift | 2 +- .../Screens/UserProfileScreen/UserProfileScreenModels.swift | 4 ++-- .../UserProfileScreen/UserProfileScreenViewModel.swift | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift index cc18f87ef7..8099006e29 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift @@ -72,7 +72,7 @@ struct RoomMemberDetailsScreenViewStateBindings { } var ignoreUserAlert: IgnoreUserAlertItem? - var alertInfo: AlertInfo? + var alertInfo: AlertInfo? /// A media item that will be previewed with QuickLook. var mediaPreviewItem: MediaPreviewItem? @@ -88,7 +88,7 @@ enum RoomMemberDetailsScreenViewAction { case startCall(roomID: String) } -enum RoomMemberDetailsScreenError: Hashable { +enum RoomMemberDetailsScreenAlertType: Hashable { case failedOpeningDirectChat case createDirectChatConfirmation case unknown diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 68d85c2e4c..13c7a96325 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -184,7 +184,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro switch await clientProxy.directRoomForUserID(roomMemberProxy.userID) { case .success(let roomID): - if let roomID = roomID { + if let roomID { actionsSubject.send(.openDirectChat(roomID: roomID)) } else { let string = roomMemberProxy.displayName ?? roomMemberProxy.userID diff --git a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift index 9158d65983..657f397f21 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenModels.swift @@ -35,7 +35,7 @@ struct UserProfileScreenViewState: BindableState { } struct UserProfileScreenViewStateBindings { - var alertInfo: AlertInfo? + var alertInfo: AlertInfo? /// A media item that will be previewed with QuickLook. var mediaPreviewItem: MediaPreviewItem? @@ -48,7 +48,7 @@ enum UserProfileScreenViewAction { case dismiss } -enum UserProfileScreenError: Hashable { +enum UserProfileScreenAlertType: Hashable { case failedOpeningDirectChat case unknown case createDirectChatConfirmation diff --git a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift index bac0dc7860..3464001252 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift @@ -118,7 +118,7 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr switch await clientProxy.directRoomForUserID(userProfile.userID) { case .success(let roomID): - if let roomID = roomID { + if let roomID { actionsSubject.send(.openDirectChat(roomID: roomID)) } else { let string = userProfile.displayName ?? userProfile.userID