diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 34d2f4657375e1a4baf78a7811a96a64d2490571..c864b65ae011a37f81518206f61a791e5e93ec04 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -58,11 +58,16 @@ let package = Package( url: "https://github.com/pointfreeco/swift-custom-dump.git", .upToNextMajor(from: "0.5.2") ), + .package( + url: "https://github.com/kean/Pulse.git", + .upToNextMajor(from: "2.1.2") + ), ], targets: [ .target( name: "AppCore", dependencies: [ + .product(name: "Pulse", package: "Pulse"), .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXDatabase", package: "client-ios-db"), @@ -98,6 +103,7 @@ let package = Package( .target(name: "WelcomeFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), + .product(name: "PulseUI", package: "Pulse"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXModels", package: "client-ios-db"), ], diff --git a/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift new file mode 100644 index 0000000000000000000000000000000000000000..6766d98d4f6ef506112e5cee8d6efa060ec9b214 --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift @@ -0,0 +1,45 @@ +import Foundation +import Pulse +import XCTestDynamicOverlay + +public struct Logger { + public enum Message: Equatable { + case error(NSError) + } + + public var run: (Message, String, String, UInt) -> Void + + public func callAsFunction( + _ msg: Message, + file: String = #file, + function: String = #function, + line: UInt = #line + ) { + run(msg, file, function, line) + } +} + +extension Logger { + public static func live() -> Logger { + Logger { msg, file, function, line in + switch msg { + case .error(let error): + LoggerStore.shared.storeMessage( + label: "xx-messenger", + level: .error, + message: error.localizedDescription, + metadata: [:], + file: file, + function: function, + line: line + ) + } + } + } +} + +extension Logger { + public static let unimplemented = Logger( + run: XCTUnimplemented("\(Self.self).error") + ) +} diff --git a/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift b/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift new file mode 100644 index 0000000000000000000000000000000000000000..d46cdc6d1a61adde2ace4c7fd6aebb3a1cfedd16 --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift @@ -0,0 +1,42 @@ +import SwiftUI + +struct ShakeViewModifier: ViewModifier { + var action: () -> Void + + func body(content: Content) -> some View { + content.onReceive( + NotificationCenter.default.publisher( + for: UIDevice.deviceDidShakeNotification + ), + perform: { _ in + action() + } + ) + } +} + +extension View { + public func onShake(perform action: @escaping () -> Void) -> some View { + modifier(ShakeViewModifier(action: action)) + } +} + +extension UIDevice { + static let deviceDidShakeNotification = Notification.Name( + rawValue: "deviceDidShakeNotification" + ) +} + +extension UIWindow { + open override func motionEnded( + _ motion: UIEvent.EventSubtype, + with event: UIEvent? + ) { + super.motionEnded(motion, with: event) + guard motion == .motionShake else { return } + NotificationCenter.default.post( + name: UIDevice.deviceDidShakeNotification, + object: nil + ) + } +} diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 3c75f40afb41be86846967f43d2b8fb0f8462b8c..c79d5b935e7f12255b852278256742608695734e 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -90,6 +90,7 @@ extension AppEnvironment { messenger: messenger, db: dbManager.getDB ), + log: .live(), mainQueue: mainQueue, bgQueue: bgQueue, welcome: { diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift index 9424cbf2991248fa6369fb44a2a23982ff6fe8d8..caae3368d778873e89af41f51a8a9ecce581866c 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift @@ -50,6 +50,7 @@ struct AppEnvironment { var messenger: Messenger var authHandler: AuthCallbackHandler var messageListener: MessageListenerHandler + var log: Logger var mainQueue: AnySchedulerOf<DispatchQueue> var bgQueue: AnySchedulerOf<DispatchQueue> var welcome: () -> WelcomeEnvironment @@ -63,6 +64,7 @@ extension AppEnvironment { messenger: .unimplemented, authHandler: .unimplemented, messageListener: .unimplemented, + log: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, welcome: { .unimplemented }, @@ -87,10 +89,10 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> } cancellables.append(env.authHandler(onError: { error in - // TODO: handle error + env.log(.error(error as NSError)) })) cancellables.append(env.messageListener(onError: { error in - // TODO: handle error + env.log(.error(error as NSError)) })) let isLoaded = env.messenger.isLoaded() diff --git a/Examples/xx-messenger/Sources/AppFeature/AppView.swift b/Examples/xx-messenger/Sources/AppFeature/AppView.swift index 64a8411df29fe65ebab6b403ae9f2c88438d140c..57983b1dd0b826321f3dac8c120637736ff64b60 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppView.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppView.swift @@ -1,11 +1,13 @@ import ComposableArchitecture import HomeFeature +import PulseUI import RestoreFeature import SwiftUI import WelcomeFeature struct AppView: View { let store: Store<AppState, AppAction> + @State var isPresentingPulse = false enum ViewState: Equatable { case loading @@ -119,6 +121,15 @@ struct AppView: View { .animation(.default, value: viewStore.state) .task { viewStore.send(.start) } } + .onShake { + isPresentingPulse = true + } + .fullScreenCover(isPresented: $isPresentingPulse) { + PulseUI.MainView( + store: .shared, + onDismiss: { isPresentingPulse = false } + ) + } } } diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift index 93d719030e6bd5554465078f957f1e0412c3da76..5bda4eb3f27c96014321e14766017b81a9f05f4e 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift @@ -351,6 +351,9 @@ final class AppFeatureTests: XCTestCase { actions.append(.didCancelMessageListener) } } + store.environment.log.run = { msg, _, _, _ in + actions.append(.didLog(msg)) + } store.send(.start) @@ -381,15 +384,21 @@ final class AppFeatureTests: XCTestCase { actions = [] struct AuthError: Error {} - authHandlerOnError.first?(AuthError()) + let authError = AuthError() + authHandlerOnError.first?(authError) - XCTAssertNoDifference(actions, []) + XCTAssertNoDifference(actions, [ + .didLog(.error(authError as NSError)) + ]) actions = [] struct MessageError: Error {} - messageListenerOnError.first?(MessageError()) + let messageError = MessageError() + messageListenerOnError.first?(messageError) - XCTAssertNoDifference(actions, []) + XCTAssertNoDifference(actions, [ + .didLog(.error(messageError as NSError)) + ]) actions = [] store.send(.stop) @@ -408,4 +417,5 @@ private enum Action: Equatable { case didLoadMessenger case didCancelAuthHandler case didCancelMessageListener + case didLog(Logger.Message) } diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved index 42a8b2ba71e97e1f4f1a04cdf740a1fb59c1242d..0407c096921c2a803d4776a42c53ab56a275507a 100644 --- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -36,6 +36,15 @@ "version" : "4.2.2" } }, + { + "identity" : "pulse", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Pulse.git", + "state" : { + "revision" : "786611d3094e33f27d4546b260a966352bc45fd6", + "version" : "2.1.2" + } + }, { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", @@ -90,6 +99,15 @@ "version" : "0.4.1" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c", + "version" : "1.4.4" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl",