From 3a280205bf9a861d5014b3e452acdc4c887d72d9 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 19 Oct 2022 14:30:55 +0200 Subject: [PATCH] Implement sending image in ChatFeature --- .../AppFeature/AppEnvironment+Live.swift | 5 ++ .../Sources/ChatFeature/ChatFeature.swift | 27 ++++++++ .../Sources/ChatFeature/ChatView.swift | 27 ++++++-- .../ChatFeatureTests/ChatFeatureTests.swift | 66 +++++++++++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index be768aba..2b1f9964 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -99,6 +99,11 @@ extension AppEnvironment { db: dbManager.getDB, now: Date.init ), + sendImage: .live( + messenger: messenger, + db: dbManager.getDB, + now: Date.init + ), mainQueue: mainQueue, bgQueue: bgQueue ) diff --git a/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift index 9e06c687..3538bc25 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift @@ -66,6 +66,7 @@ public enum ChatAction: Equatable, BindableAction { case didFetchMessages(IdentifiedArrayOf<ChatState.Message>) case sendTapped case sendFailed(String) + case imagePicked(Data) case dismissSendFailureTapped case binding(BindingAction<ChatState>) } @@ -75,12 +76,14 @@ public struct ChatEnvironment { messenger: Messenger, db: DBManagerGetDB, sendMessage: SendMessage, + sendImage: SendImage, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue> ) { self.messenger = messenger self.db = db self.sendMessage = sendMessage + self.sendImage = sendImage self.mainQueue = mainQueue self.bgQueue = bgQueue } @@ -88,6 +91,7 @@ public struct ChatEnvironment { public var messenger: Messenger public var db: DBManagerGetDB public var sendMessage: SendMessage + public var sendImage: SendImage public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> } @@ -98,6 +102,7 @@ extension ChatEnvironment { messenger: .unimplemented, db: .unimplemented, sendMessage: .unimplemented, + sendImage: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented ) @@ -196,6 +201,28 @@ public let chatReducer = Reducer<ChatState, ChatAction, ChatEnvironment> state.sendFailure = failure return .none + case .imagePicked(let data): + let chatId = state.id + return Effect.run { subscriber in + switch chatId { + case .contact(let recipientId): + env.sendImage( + data, + to: recipientId, + onError: { error in + subscriber.send(.sendFailed(error.localizedDescription)) + }, + completion: { + subscriber.send(completion: .finished) + } + ) + } + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + case .dismissSendFailureTapped: state.sendFailure = nil return .none diff --git a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift index 7c6c51f8..6058b61b 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift @@ -8,6 +8,7 @@ public struct ChatView: View { } let store: Store<ChatState, ChatAction> + @State var isPresentingImagePicker = false struct ViewState: Equatable { var myContactId: Data? @@ -87,12 +88,28 @@ public struct ChatView: View { )) .textFieldStyle(.roundedBorder) - Button { - viewStore.send(.sendTapped) - } label: { - Image(systemName: "paperplane.fill") + if viewStore.text.isEmpty == false { + Button { + viewStore.send(.sendTapped) + } label: { + Image(systemName: "paperplane.fill") + } + .buttonStyle(.borderedProminent) + } else { + Button { + isPresentingImagePicker = true + } label: { + Image(systemName: "photo.on.rectangle.angled") + } + .buttonStyle(.borderedProminent) + .sheet(isPresented: $isPresentingImagePicker) { + ImagePicker { image in + if let data = image.jpegData(compressionQuality: 0.7) { + viewStore.send(.imagePicked(data)) + } + } + } } - .buttonStyle(.borderedProminent) } .padding() } diff --git a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift index 492d26d3..01d0ed8f 100644 --- a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift @@ -240,4 +240,70 @@ final class ChatFeatureTests: XCTestCase { $0.sendFailure = nil } } + + func testSendImage() { + struct SendImageParams: Equatable { + var image: Data + var recipientId: Data + } + var didSendImageWithParams: [SendImageParams] = [] + var sendImageCompletion: SendImage.Completion? + + let store = TestStore( + initialState: ChatState(id: .contact("contact-id".data(using: .utf8)!)), + reducer: chatReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.sendImage.run = { image, recipientId, _, completion in + didSendImageWithParams.append(.init(image: image, recipientId: recipientId)) + sendImageCompletion = completion + } + + let image = "image-data".data(using: .utf8)! + store.send(.imagePicked(image)) + + XCTAssertNoDifference(didSendImageWithParams, [ + .init(image: image, recipientId: "contact-id".data(using: .utf8)!) + ]) + + sendImageCompletion?() + } + + func testSendImageFailure() { + var sendImageOnError: SendImage.OnError? + var sendImageCompletion: SendImage.Completion? + + let store = TestStore( + initialState: ChatState( + id: .contact("contact-id".data(using: .utf8)!) + ), + reducer: chatReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.sendImage.run = { _, _, onError, completion in + sendImageOnError = onError + sendImageCompletion = completion + } + + store.send(.imagePicked(Data())) + + let error = NSError(domain: "test", code: 123) + sendImageOnError?(error) + + store.receive(.sendFailed(error.localizedDescription)) { + $0.sendFailure = error.localizedDescription + } + + sendImageCompletion?() + + store.send(.dismissSendFailureTapped) { + $0.sendFailure = nil + } + } } -- GitLab