mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Send reaction method placeholder (#355)
* Send reaction method placeholder * Removed unnecessary emoji skin parsing * Code review fixes
This commit is contained in:
committed by
GitHub
parent
7db7eec98f
commit
1e197d48de
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,10 @@ struct MockRoomProxy: RoomProxyProtocol {
|
||||
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
|
||||
@@ -185,6 +185,12 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
||||
await Task.dispatch(on: .global()) {
|
||||
.success(())
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||
|
||||
@@ -56,6 +56,8 @@ protocol RoomProxyProtocol {
|
||||
func paginateBackwards(count: UInt) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendMessage(_ message: String, inReplyToEventId: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedSendingReaction)
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, originalItemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -103,6 +103,17 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
return .failure(.failedSendingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||
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<Void, RoomTimelineProviderError> {
|
||||
MXLog.info("Editing message: \(originalItemId)")
|
||||
|
||||
@@ -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<Void, RoomTimelineProviderError>
|
||||
|
||||
func sendReaction(_ reaction: String, for itemId: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func editMessage(_ newMessage: String, originalItemId: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomTimelineProviderError>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user