From 4273dcc3cbe3ce2ec8b8abd26515b7874d1fe111 Mon Sep 17 00:00:00 2001 From: Aleksandrs Proskurins Date: Wed, 7 Dec 2022 13:19:47 +0200 Subject: [PATCH] Send reaction method placeholder (#355) * Send reaction method placeholder * Removed unnecessary emoji skin parsing * Code review fixes --- ElementX.xcodeproj/project.pbxproj | 4 --- .../EmojiPickerScreenCoordinator.swift | 6 ++-- .../EmojiPickerScreenModels.swift | 4 +-- .../EmojiPickerScreenViewModel.swift | 6 ++-- .../View/EmojiPickerScreen.swift | 2 +- .../RoomScreen/RoomScreenCoordinator.swift | 13 ++++--- .../Screens/RoomScreen/RoomScreenModels.swift | 1 + .../RoomScreen/RoomScreenViewModel.swift | 3 ++ .../TimelineItemReactionsMenuView.swift | 7 +++- .../RoomScreen/View/TimelineTableView.swift | 6 ++-- .../Sources/Services/Emojis/EmojiItem.swift | 6 ++-- .../Services/Emojis/EmojiItemSkin.swift | 36 ------------------- .../Sources/Services/Room/MockRoomProxy.swift | 4 +++ .../Sources/Services/Room/RoomProxy.swift | 6 ++++ .../Services/Room/RoomProxyProtocol.swift | 2 ++ .../Timeline/MockRoomTimelineController.swift | 2 ++ .../Timeline/MockRoomTimelineProvider.swift | 4 +++ .../Timeline/RoomTimelineController.swift | 8 +++++ .../RoomTimelineControllerProtocol.swift | 2 ++ .../Timeline/RoomTimelineProvider.swift | 11 ++++++ .../RoomTimelineProviderProtocol.swift | 3 ++ .../UserSessionFlowCoordinator.swift | 2 +- .../UITests/UITestsAppCoordinator.swift | 4 +-- UnitTests/Sources/EmojiProviderTests.swift | 26 +++++--------- 24 files changed, 88 insertions(+), 80 deletions(-) delete mode 100644 ElementX/Sources/Services/Emojis/EmojiItemSkin.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 0e91df701..d6c26f97f 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -163,7 +163,6 @@ 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; }; 5D04B17929378AB300FD5B00 /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 5D04B17829378AB300FD5B00 /* apple_emojis_data.json */; }; 5D04B17B29378D3600FD5B00 /* EmojiMartEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17A29378D3600FD5B00 /* EmojiMartEmoji.swift */; }; - 5D04B17D2937ADE300FD5B00 /* EmojiItemSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */; }; 5D04B17F293A333600FD5B00 /* EmojiPickerSearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17E293A333600FD5B00 /* EmojiPickerSearchFieldView.swift */; }; 5D04B181293A337400FD5B00 /* EmojiPickerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B180293A337400FD5B00 /* EmojiPickerHeaderView.swift */; }; 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; }; @@ -689,7 +688,6 @@ 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = ""; }; 5D04B17829378AB300FD5B00 /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = ""; }; 5D04B17A29378D3600FD5B00 /* EmojiMartEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartEmoji.swift; sourceTree = ""; }; - 5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemSkin.swift; sourceTree = ""; }; 5D04B17E293A333600FD5B00 /* EmojiPickerSearchFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerSearchFieldView.swift; sourceTree = ""; }; 5D04B180293A337400FD5B00 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = ""; }; 5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = ""; }; @@ -1274,7 +1272,6 @@ 5DE2282D293F2CF6001790FD /* EmojiLoaderProtocol.swift */, 5DE2282B293F29FC001790FD /* EmojiProvider.swift */, 5BACB442D02C878293C04837 /* EmojiMart */, - 5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */, ); path = Emojis; sourceTree = ""; @@ -3001,7 +2998,6 @@ A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */, 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */, 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */, - 5D04B17D2937ADE300FD5B00 /* EmojiItemSkin.swift in Sources */, E290C78E7F09F47FD2662986 /* Task.swift in Sources */, 43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */, 63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */, diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenCoordinator.swift b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenCoordinator.swift index 194f05740..9437bd37d 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenCoordinator.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenCoordinator.swift @@ -22,7 +22,7 @@ struct EmojiPickerScreenCoordinatorParameters { } enum EmojiPickerScreenCoordinatorAction { - case selectEmoji(emojiId: String, itemId: String) + case emojiSelected(emoji: String, itemId: String) } final class EmojiPickerScreenCoordinator: CoordinatorProtocol { @@ -42,8 +42,8 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol { guard let self else { return } MXLog.debug("EmojiPickerScreenViewModel did complete with result: \(action).") switch action { - case let .selectEmoji(emojiId: emojiId): - self.callback?(.selectEmoji(emojiId: emojiId, itemId: self.parameters.itemId)) + case let .emojiSelected(emoji: emoji): + self.callback?(.emojiSelected(emoji: emoji, itemId: self.parameters.itemId)) } } } diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenModels.swift b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenModels.swift index b2f75111d..12b267b54 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenModels.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenModels.swift @@ -17,7 +17,7 @@ import Foundation enum EmojiPickerScreenViewModelAction { - case selectEmoji(emojiId: String) + case emojiSelected(emoji: String) } struct EmojiPickerScreenViewState: BindableState { @@ -26,7 +26,7 @@ struct EmojiPickerScreenViewState: BindableState { enum EmojiPickerScreenViewAction { case search(searchString: String) - case emojiSelected(emoji: EmojiPickerEmojiViewData) + case emojiTapped(emoji: EmojiPickerEmojiViewData) } struct EmojiPickerEmojiCategoryViewData: Identifiable { diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenViewModel.swift b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenViewModel.swift index 3ca576521..43f4d0d4e 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenViewModel.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/EmojiPickerScreenViewModel.swift @@ -37,8 +37,8 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr case let .search(searchString: searchString): let categories = await emojiProvider.getCategories(searchString: searchString) state.categories = convert(emojiCategories: categories) - case let .emojiSelected(emoji: emoji): - callback?(.selectEmoji(emojiId: emoji.id)) + case let .emojiTapped(emoji: emoji): + callback?(.emojiSelected(emoji: emoji.value)) } } @@ -59,7 +59,7 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr guard let firstSkin = emojiItem.skins.first else { return nil } - return EmojiPickerEmojiViewData(id: emojiItem.id, value: firstSkin.value) + return EmojiPickerEmojiViewData(id: emojiItem.id, value: firstSkin) } return EmojiPickerEmojiCategoryViewData(id: emojiCategory.id, emojis: emojisViewData) diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift index ec2cd9a7a..e12f10ad3 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift @@ -36,7 +36,7 @@ struct EmojiPickerScreen: View { Text(emoji.value) .frame(width: 45, height: 45) .onTapGesture { - context.send(viewAction: .emojiSelected(emoji: emoji)) + context.send(viewAction: .emojiTapped(emoji: emoji)) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index 645117624..7f0bced60 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -22,7 +22,7 @@ struct RoomScreenCoordinatorParameters { let mediaProvider: MediaProviderProtocol let roomName: String? let roomAvatarUrl: String? - let emojiProvide: EmojiProviderProtocol + let emojiProvider: EmojiProviderProtocol } final class RoomScreenCoordinator: CoordinatorProtocol { @@ -113,7 +113,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol { } private func displayEmojiPickerScreen(for itemId: String) { - guard let emojiProvider = parameters?.emojiProvide else { + guard let emojiProvider = parameters?.emojiProvider, + let timelineController = parameters?.timelineController else { fatalError() } let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider, @@ -121,12 +122,14 @@ final class RoomScreenCoordinator: CoordinatorProtocol { let coordinator = EmojiPickerScreenCoordinator(parameters: params) coordinator.callback = { [weak self] action in switch action { - case let .selectEmoji(emojiId: emojiId, itemId: itemId): + case let .emojiSelected(emoji: emoji, itemId: itemId): self?.navigationController.dismissSheet() - MXLog.debug("Save \(emojiId) for \(itemId)") + Task { + await timelineController.sendReaction(emoji, for: itemId) + } } } - + navigationController.presentSheet(coordinator) } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index cd3868adb..22a3073dc 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -31,6 +31,7 @@ enum RoomScreenComposerMode: Equatable { enum RoomScreenViewAction { case displayEmojiPicker(itemId: String) + case emojiTapped(emoji: String, itemId: String) case paginateBackwards case itemAppeared(id: String) case itemDisappeared(id: String) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index aa5379fe9..19abe2a71 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -117,6 +117,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol state.composerMode = .default case .cancelEdit: state.composerMode = .default + case .emojiTapped(let emoji, let itemId): + await timelineController.sendReaction(emoji, for: itemId) + state.displayReactionsMenuForItemId = "" } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemReactionsMenuView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemReactionsMenuView.swift index f9e9b0db8..b7e5602ec 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemReactionsMenuView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemReactionsMenuView.swift @@ -19,13 +19,18 @@ import SwiftUI struct TimelineItemReactionsMenuView: View { private let emojis = ["👍🏼", "👎🏼", "😄", "🙏🏼", "😇"] + var onEmojiSelected: ((String) -> Void)? var onDisplayEmojiPicker: (() -> Void)? var body: some View { HStack { HStack(spacing: 10) { ForEach(emojis, id: \.self) { emoji in - Text(emoji) + Button { + onEmojiSelected?(emoji) + } label: { + Text(emoji) + } } } .padding(10) diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineTableView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineTableView.swift index 15efaf050..e39b8099b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineTableView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineTableView.swift @@ -183,9 +183,11 @@ struct TimelineTableView: UIViewRepresentable { cell.contentConfiguration = UIHostingConfiguration { VStack { if viewModelContext.viewState.displayReactionsMenuForItemId == timelineItem.id { - TimelineItemReactionsMenuView { + TimelineItemReactionsMenuView(onEmojiSelected: { emoji in + viewModelContext.send(viewAction: .emojiTapped(emoji: emoji, itemId: timelineItem.id)) + }, onDisplayEmojiPicker: { viewModelContext.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id)) - } + }) } timelineItem .frame(maxWidth: .infinity, alignment: .leading) diff --git a/ElementX/Sources/Services/Emojis/EmojiItem.swift b/ElementX/Sources/Services/Emojis/EmojiItem.swift index 6b8abee13..a6e1e6a60 100644 --- a/ElementX/Sources/Services/Emojis/EmojiItem.swift +++ b/ElementX/Sources/Services/Emojis/EmojiItem.swift @@ -20,7 +20,7 @@ struct EmojiItem: Equatable, Identifiable { var id: String let name: String let keywords: [String] - let skins: [EmojiItemSkin] + let skins: [String] } extension EmojiItem { @@ -28,8 +28,8 @@ extension EmojiItem { id = emojiMart.id name = emojiMart.name keywords = emojiMart.keywords - skins = emojiMart.skins.compactMap { emojiMartEmojiSkin in - EmojiItemSkin(from: emojiMartEmojiSkin) + skins = emojiMart.skins.map { emojiMartEmojiSkin in + emojiMartEmojiSkin.native } } } diff --git a/ElementX/Sources/Services/Emojis/EmojiItemSkin.swift b/ElementX/Sources/Services/Emojis/EmojiItemSkin.swift deleted file mode 100644 index 2aedd83c3..000000000 --- a/ElementX/Sources/Services/Emojis/EmojiItemSkin.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright 2022 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 EmojiItemSkin: Equatable { - let value: String - - init?(from emojiMartEmojiSkin: EmojiMartEmojiSkin) { - let unicodeStringComponents = emojiMartEmojiSkin.unified.components(separatedBy: "-") - - var emoji = "" - - for unicodeStringComponent in unicodeStringComponents { - guard let unicodeCodePoint = Int(unicodeStringComponent, radix: 16), - let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) else { - return nil - } - emoji.append(String(emojiUnicodeScalar)) - } - value = emoji - } -} diff --git a/ElementX/Sources/Services/Room/MockRoomProxy.swift b/ElementX/Sources/Services/Room/MockRoomProxy.swift index 05d451160..8cf2a94cf 100644 --- a/ElementX/Sources/Services/Room/MockRoomProxy.swift +++ b/ElementX/Sources/Services/Room/MockRoomProxy.swift @@ -64,6 +64,10 @@ struct MockRoomProxy: RoomProxyProtocol { func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result { .failure(.failedSendingMessage) } + + func sendReaction(_ reaction: String, for eventId: String) async -> Result { + .failure(.failedSendingMessage) + } func editMessage(_ newMessage: String, originalEventId: String) async -> Result { .failure(.failedSendingMessage) diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 88465c0bc..5f323ed15 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -185,6 +185,12 @@ class RoomProxy: RoomProxyProtocol { } } } + + func sendReaction(_ reaction: String, for eventId: String) async -> Result { + await Task.dispatch(on: .global()) { + .success(()) + } + } func editMessage(_ newMessage: String, originalEventId: String) async -> Result { sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true) diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index dd577b387..cbaae77c0 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -56,6 +56,8 @@ protocol RoomProxyProtocol { func paginateBackwards(count: UInt) async -> Result func sendMessage(_ message: String, inReplyToEventId: String?) async -> Result + + func sendReaction(_ reaction: String, for eventId: String) async -> Result func editMessage(_ newMessage: String, originalEventId: String) async -> Result diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift index dd46e6076..6ff81d646 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift @@ -115,6 +115,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { func sendMessage(_ message: String) async { } func sendReply(_ message: String, to itemId: String) async { } + + func sendReaction(_ reaction: String, for itemId: String) async { } func editMessage(_ newMessage: String, of itemId: String) async { } diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift index a2f9460aa..b8af00635 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineProvider.swift @@ -31,6 +31,10 @@ struct MockRoomTimelineProvider: RoomTimelineProviderProtocol { .failure(.failedSendingMessage) } + func sendReaction(_ reaction: String, for itemId: String) async -> Result { + .failure(.failedSendingReaction) + } + func editMessage(_ newMessage: String, originalItemId: String) async -> Result { .failure(.failedSendingMessage) } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index 48265c914..7a5fffaba 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -108,6 +108,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { func processItemDisappearance(_ itemId: String) { } + // swiftlint:disable:next cyclomatic_complexity func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction { guard let timelineItem = timelineItems.first(where: { $0.id == itemId }) else { return .none @@ -162,6 +163,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { break } } + + func sendReaction(_ reaction: String, for itemId: String) async { + switch await timelineProvider.sendReaction(reaction, for: itemId) { + default: + break + } + } func editMessage(_ newMessage: String, of itemId: String) async { switch await timelineProvider.editMessage(newMessage, originalItemId: itemId) { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift index 446935e45..a7da91414 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift @@ -55,6 +55,8 @@ protocol RoomTimelineControllerProtocol { func sendReply(_ message: String, to itemId: String) async func editMessage(_ newMessage: String, of itemId: String) async + + func sendReaction(_ reaction: String, for itemId: String) async func redact(_ eventID: String) async diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index 43335a451..801cc14bb 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -103,6 +103,17 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { return .failure(.failedSendingMessage) } } + + func sendReaction(_ reaction: String, for itemId: String) async -> Result { + switch await roomProxy.sendReaction(reaction, for: itemId) { + case .success: + MXLog.info("Finished sending reaction") + return .success(()) + case .failure(let error): + MXLog.error("Failed sending reaction with error: \(error)") + return .failure(.failedSendingReaction) + } + } func editMessage(_ newMessage: String, originalItemId: String) async -> Result { MXLog.info("Editing message: \(originalItemId)") diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift index a50a12969..5433952b8 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift @@ -21,6 +21,7 @@ enum RoomTimelineProviderError: Error { case noMoreMessagesToBackPaginate case failedPaginatingBackwards case failedSendingMessage + case failedSendingReaction case failedRedactingItem case generic } @@ -34,6 +35,8 @@ protocol RoomTimelineProviderProtocol { func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result + func sendReaction(_ reaction: String, for itemId: String) async -> Result + func editMessage(_ newMessage: String, originalItemId: String) async -> Result func redact(_ eventID: String) async -> Result diff --git a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift index 05e4da6bc..4309f9750 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift @@ -150,7 +150,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol { mediaProvider: userSession.mediaProvider, roomName: roomProxy.displayName ?? roomProxy.name, roomAvatarUrl: roomProxy.avatarURL, - emojiProvide: emojiProvider) + emojiProvider: emojiProvider) let coordinator = RoomScreenCoordinator(parameters: parameters) navigationController.push(coordinator) { [weak self] in diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 7b23f2fae..e0c0729d8 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -122,7 +122,7 @@ class MockScreen: Identifiable { mediaProvider: MockMediaProvider(), roomName: "Some room name", roomAvatarUrl: nil, - emojiProvide: EmojiProvider()) + emojiProvider: EmojiProvider()) return RoomScreenCoordinator(parameters: parameters) case .roomEncryptedWithAvatar: let parameters = RoomScreenCoordinatorParameters(navigationController: navigationController, @@ -130,7 +130,7 @@ class MockScreen: Identifiable { mediaProvider: MockMediaProvider(), roomName: "Some room name", roomAvatarUrl: "mock_url", - emojiProvide: EmojiProvider()) + emojiProvider: EmojiProvider()) return RoomScreenCoordinator(parameters: parameters) case .sessionVerification: let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy()) diff --git a/UnitTests/Sources/EmojiProviderTests.swift b/UnitTests/Sources/EmojiProviderTests.swift index 1c9b50a7d..a67fb0857 100644 --- a/UnitTests/Sources/EmojiProviderTests.swift +++ b/UnitTests/Sources/EmojiProviderTests.swift @@ -28,7 +28,7 @@ final class EmojiProviderTests: XCTestCase { } func test_whenEmojisLoaded_categoriesAreLoadedFromLoader() async throws { - let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()]) + let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: ["🙂"]) let category = EmojiCategory(id: "test", emojis: [item]) emojiLoaderMock.categories = [category] let categories = await sut.getCategories() @@ -36,7 +36,7 @@ final class EmojiProviderTests: XCTestCase { } func test_whenEmojisLoadedAndSearchStringEmpty_allCategoriesReturned() async throws { - let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()]) + let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: ["🙂"]) let category = EmojiCategory(id: "test", emojis: [item]) emojiLoaderMock.categories = [category] let categories = await sut.getCategories(searchString: "") @@ -45,9 +45,9 @@ final class EmojiProviderTests: XCTestCase { func test_whenEmojisLoadedSecondTime_cachedValuesAreUsed() async throws { let categoriesForFirstLoad = [EmojiCategory(id: "test", - emojis: [EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()])])] + emojis: [EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: ["🙂"])])] let categoriesForSecondLoad = [EmojiCategory(id: "test2", - emojis: [EmojiItem(id: "test2", name: "test2", keywords: ["3", "4"], skins: [try meltingFaceEmoji()])])] + emojis: [EmojiItem(id: "test2", name: "test2", keywords: ["3", "4"], skins: ["🫠"])])] emojiLoaderMock.categories = categoriesForFirstLoad _ = await sut.getCategories() emojiLoaderMock.categories = categoriesForSecondLoad @@ -62,38 +62,30 @@ final class EmojiProviderTests: XCTestCase { emojis: [EmojiItem(id: "\(searchString)_123", name: "emoji0", keywords: ["key1", "key1"], - skins: [try slightlySmilingFaceEmoji()]), + skins: ["🙂"]), EmojiItem(id: "emoji_1", name: searchString, keywords: ["key1", "key1"], - skins: [try slightlySmilingFaceEmoji()]), + skins: ["🙂"]), EmojiItem(id: "emoji_2", name: "emoji2", keywords: ["key1", "\(searchString)_123"], - skins: [try slightlySmilingFaceEmoji()]), + skins: ["🙂"]), EmojiItem(id: "emoji_3", name: "emoji_3", keywords: ["key1", "key1"], - skins: [try slightlySmilingFaceEmoji()])])) + skins: ["🙂"])])) categories.append(EmojiCategory(id: "test", emojis: [EmojiItem(id: "\(searchString)_123", name: "emoji0", keywords: ["key1", "key1"], - skins: [try slightlySmilingFaceEmoji()])])) + skins: ["🙂"])])) emojiLoaderMock.categories = categories _ = await sut.getCategories() let result = await sut.getCategories(searchString: searchString) XCTAssertEqual(result.count, 2) XCTAssertEqual(result.first?.emojis.count, 3) } - - private func slightlySmilingFaceEmoji() throws -> EmojiItemSkin { - try XCTUnwrap(EmojiItemSkin(from: EmojiMartEmojiSkin(unified: "1f642", native: "🙂"))) - } - - private func meltingFaceEmoji() throws -> EmojiItemSkin { - try XCTUnwrap(EmojiItemSkin(from: EmojiMartEmojiSkin(unified: "1fae0", native: "🫠"))) - } } private class EmojiLoaderMock: EmojiLoaderProtocol {