diff --git a/BACKGROUND_SYNCING.md b/BACKGROUND_SYNCING.md new file mode 100644 index 0000000..87d0387 --- /dev/null +++ b/BACKGROUND_SYNCING.md @@ -0,0 +1,70 @@ +# Background Syncing + +## Sources +We encourange you to watch WWDC videos: + - [Advances in App Background Execution]: https://developer.apple.com/videos/play/wwdc2019/707 + - [Background execution demystified]: https://developer.apple.com/videos/play/wwdc2020/10063 + +## Implementation details +There are 4 different APIs and types of background tasks. Each one is specific and can be used for different scenarios. Synchronization of the blockchain data is time and memory consuming operation. Therefore the `BGProcessingTask` has been used. This type of task is designed to run for a longer time when certain conditions are met (watch Background execution demystified). + +### Steps to make it work +1. Add a capability of `background modes` in the settings of the xcode project. +2. Turn the `Background Processing` mode on in the new capability. +3. Add `Permitted background task scheduler identifiers` to the info.plist. +4. Create the ID for the background task in the newly created array. +5. Register the BGTask in `application.didFinishLaunchingWithOptions` +```Swift +BGTaskScheduler.shared.register( + forTaskWithIdentifier: , + using: DispatchQueue.main +) { task in + // see the next steps +} +``` +Note: The queue is an optional and most tutorials leave the parameter `nil` but Zashi requires main thread processing due to UI layer - therefore we pass `DispatchQueue.main`. +6. Call a method that schedules the task execution. +7. Start the synchronizer. +8. Set the expiration closure and stop the synchronizer inside it +```swift +task.expirationHandler = { + synchronizer.stop() +} +``` +9. The body of the registered task summarized: +```swift +BGTaskScheduler.shared.register(...) { task in + scheduleTask() // 6 + + synchronizer.start() // 7 + + task.expirationHandler = { + synchronizer.stop() // 8 + } +} +``` +10. Call `scheduleTask()` when app goes to the background so there is the initial scheduling done, the next one will be handled by the closure of the registered task. The method usually consists of: +```Swift +let request = BGProcessingTaskRequest(identifier: ) + +request.earliestBeginDate = +request.requiresExternalPower = true // optional, we require the iPhone to be connected to the power +request.requiresNetworkConnectivity = true // required + +do { + try BGTaskScheduler.shared.submit(request) +} catch { // handle error } +``` +11. Last step is to call `.setTaskCompleted(success: )` on the BGTask when the work is done. This is required by the system no matter what. We call it with `true` when the synchronizer finishes the work (up-to-date state) and with `false` for other or failed reasons (stopped state, error state, etc.). + +You can see specific details of the Zashi implementation in: +- Xcode project settings, steps 1-4. +- AppDelegate.swift file, steps 5-9. +- SecantApp.swift file, step 10. +- RootInitialization.swift, step 11. + +## Gotchas +- The `requiresNetworkConnectivity` flag doesn't specify or deal with the type of connectivity. It simply allows scheduling when the iPhone is connected to the internet. We deal with it when the task is triggered. The custom check wheather the wifi is or is not connected preceeds the start of the synchronizer. +- When the app is killed by a user in the app switcher, the scheduled BGTask is deleted. So the BGTask is triggered at the scheduled time only when the app is suspended or killed by the system. Explicit termination of the app by a user leads to termination of any background processing. + + diff --git a/CHANGELOG.md b/CHANGELOG.md index c665822..3fbb3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ directly impact users rather than highlighting other crucial architectural updat ### Added - The exported logs also show the shielded balances (total & verified) for every finished sync metric. +- Synchronization in the background. When the iPhone is connected to the power and wifi, the background task will try to synchronize randomly between 3-4am. ### Fixed - The export buttons are disabled when exporting of the private data is in progress. diff --git a/modules/Sources/Features/Root/RootDestination.swift b/modules/Sources/Features/Root/RootDestination.swift index 6bfa6c3..b6f26c9 100644 --- a/modules/Sources/Features/Root/RootDestination.swift +++ b/modules/Sources/Features/Root/RootDestination.swift @@ -105,7 +105,7 @@ extension RootReducer { state.splashAppeared = true return .none - case .tabs, .initialization, .onboarding, .sandbox, .updateStateAfterConfigUpdate, .alert, .phraseDisplay, + case .tabs, .initialization, .onboarding, .sandbox, .updateStateAfterConfigUpdate, .alert, .phraseDisplay, .synchronizerStateChanged, .welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .exportLogs, .confirmationDialog: return .none } diff --git a/modules/Sources/Features/Root/RootInitialization.swift b/modules/Sources/Features/Root/RootInitialization.swift index 8568015..906963b 100644 --- a/modules/Sources/Features/Root/RootInitialization.swift +++ b/modules/Sources/Features/Root/RootInitialization.swift @@ -28,6 +28,7 @@ extension RootReducer { case nukeWalletRequest case respondToWalletInitializationState(InitializationState) case synchronizerStartFailed(ZcashError) + case registerForSynchronizersUpdate case retryStart case walletConfigChanged(WalletConfig) } @@ -38,7 +39,15 @@ extension RootReducer { switch action { case .initialization(.appDelegate(.didEnterBackground)): sdkSynchronizer.stop() - return .none + state.bgTask?.setTaskCompleted(success: false) + state.bgTask = nil + return .cancel(id: CancelStateId.timer) + + case .initialization(.appDelegate(.backgroundTask(let task))): + state.bgTask = task + return .run { send in + await send(.initialization(.retryStart)) + } case .initialization(.appDelegate(.willEnterForeground)): return .run { send in @@ -46,6 +55,37 @@ extension RootReducer { await send(.initialization(.retryStart)) } + case .synchronizerStateChanged(let latestState): + guard state.bgTask != nil else { + return .none + } + + let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.syncStatus) + + var finishBGTask = false + var successOfBGTask = false + + switch snapshot.syncStatus { + case .upToDate: + successOfBGTask = true + finishBGTask = true + case .stopped, .error: + successOfBGTask = false + finishBGTask = true + default: break + } + + LoggerProxy.event("BGTask .synchronizerStateChanged(let latestState): \(snapshot.syncStatus)") + + if finishBGTask { + LoggerProxy.event("BGTask setTaskCompleted(success: \(successOfBGTask)) from TCA") + state.bgTask?.setTaskCompleted(success: successOfBGTask) + state.bgTask = nil + return .cancel(id: CancelStateId.timer) + } + + return .none + case .initialization(.synchronizerStartFailed): return .none @@ -54,13 +94,28 @@ extension RootReducer { guard sdkSynchronizer.latestState().syncStatus.isPrepared else { return .none } - return .run { send in + return .run { [state] send in do { try await sdkSynchronizer.start(true) + if state.bgTask != nil { + LoggerProxy.event("BGTask synchronizer.start() PASSED") + } + await send(.initialization(.registerForSynchronizersUpdate)) } catch { + if state.bgTask != nil { + LoggerProxy.event("BGTask synchronizer.start() failed \(error.toZcashError())") + } await send(.initialization(.synchronizerStartFailed(error.toZcashError()))) } } + + case .initialization(.registerForSynchronizersUpdate): + return .publisher { + sdkSynchronizer.stateStream() + .throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true) + .map(RootReducer.Action.synchronizerStateChanged) + } + .cancellable(id: CancelStateId.timer, cancelInFlight: true) case .initialization(.appDelegate(.didFinishLaunching)): // TODO: [#704], trigger the review request logic when approved by the team, @@ -174,7 +229,7 @@ extension RootReducer { case .initialization(.initializationSuccessfullyDone(let uAddress)): state.tabsState.addressDetailsState.uAddress = uAddress - return .none + return .send(.initialization(.registerForSynchronizersUpdate)) case .initialization(.checkBackupPhraseValidation): guard let storedWallet = state.storedWallet else { diff --git a/modules/Sources/Features/Root/RootStore.swift b/modules/Sources/Features/Root/RootStore.swift index a4bb609..3d8c42b 100644 --- a/modules/Sources/Features/Root/RootStore.swift +++ b/modules/Sources/Features/Root/RootStore.swift @@ -17,12 +17,14 @@ import Tabs import CrashReporter import ReadTransactionsStorage import RecoveryPhraseDisplay +import BackgroundTasks public typealias RootStore = Store public typealias RootViewStore = ViewStore public struct RootReducer: Reducer { enum CancelId { case timer } + enum CancelStateId { case timer } enum SynchronizerCancelId { case timer } enum WalletConfigCancelId { case timer } let tokenName: String @@ -31,6 +33,7 @@ public struct RootReducer: Reducer { public struct State: Equatable { @PresentationState public var alert: AlertState? public var appInitializationState: InitializationState = .uninitialized + public var bgTask: BGProcessingTask? @PresentationState public var confirmationDialog: ConfirmationDialogState? public var debugState: DebugState public var destinationState: DestinationState @@ -92,6 +95,7 @@ public struct RootReducer: Reducer { case splashFinished case splashRemovalRequested case sandbox(SandboxReducer.Action) + case synchronizerStateChanged(SynchronizerState) case updateStateAfterConfigUpdate(WalletConfig) case walletConfigLoaded(WalletConfig) case welcome(WelcomeReducer.Action) diff --git a/modules/Sources/Models/AppDelegate.swift b/modules/Sources/Models/AppDelegateAction.swift similarity index 70% rename from modules/Sources/Models/AppDelegate.swift rename to modules/Sources/Models/AppDelegateAction.swift index 58f8d77..7ebe7f9 100644 --- a/modules/Sources/Models/AppDelegate.swift +++ b/modules/Sources/Models/AppDelegateAction.swift @@ -1,14 +1,16 @@ // -// AppDelegate.swift +// AppDelegateAction.swift // secant-testnet // // Created by Lukáš Korba on 27.03.2022. // import Foundation +import BackgroundTasks public enum AppDelegateAction: Equatable { case didFinishLaunching case didEnterBackground case willEnterForeground + case backgroundTask(BGProcessingTask) } diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index ba0b6d1..2b5d245 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -83,6 +83,8 @@ 9EB35D632A31F1DD00A2149B /* Root in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D622A31F1DD00A2149B /* Root */; }; 9EB35D6A2A3A2D7B00A2149B /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D692A3A2D7B00A2149B /* Utils */; }; 9EB35D6C2A3A2D9200A2149B /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D6B2A3A2D9200A2149B /* Utils */; }; + 9EEB06C82B405A0400EEE50F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */; }; + 9EEB06C92B405A0400EEE50F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -168,6 +170,7 @@ 9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencySelectionTests.swift; sourceTree = ""; }; 9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAmountInputTests.swift; sourceTree = ""; }; 9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressInputTests.swift; sourceTree = ""; }; + 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = ""; }; 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -242,6 +245,7 @@ isa = PBXGroup; children = ( 9E7FE0B6282D1D9800C374E8 /* Resources */, + 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */, 0D4E7A0826B364170058B01E /* SecantApp.swift */, 9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */, 0DEF4766299EA5920032708B /* secant-mainnet-Info.plist */, @@ -910,6 +914,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9EEB06C92B405A0400EEE50F /* AppDelegate.swift in Sources */, 0D26AF24299E8196005260EE /* SecantApp.swift in Sources */, 9E2706682AFF99F5000DA6EC /* ReadTransactionsStorageModel.xcdatamodeld in Sources */, ); @@ -919,6 +924,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9EEB06C82B405A0400EEE50F /* AppDelegate.swift in Sources */, 0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */, 9E2706672AFF99F5000DA6EC /* ReadTransactionsStorageModel.xcdatamodeld in Sources */, ); @@ -1011,7 +1017,7 @@ CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; - DEVELOPMENT_TEAM = RLPRR8CPQG; + DEVELOPMENT_TEAM = W5KABFU8SV; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "secant/secant-mainnet-Info.plist"; @@ -1039,7 +1045,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; - DEVELOPMENT_TEAM = RLPRR8CPQG; + DEVELOPMENT_TEAM = W5KABFU8SV; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "secant/secant-mainnet-Info.plist"; diff --git a/secant/AppDelegate.swift b/secant/AppDelegate.swift new file mode 100644 index 0000000..2d46300 --- /dev/null +++ b/secant/AppDelegate.swift @@ -0,0 +1,189 @@ +// +// AppDelegate.swift +// secant +// +// Created by Lukáš Korba on 30.12.2023. +// + +import SwiftUI +import ComposableArchitecture +import ZcashLightClientKit +import Network + +import Utils +import Root +import BackgroundTasks + +final class AppDelegate: NSObject, UIApplicationDelegate { + private let bcgTaskId = "co.electriccoin.power_wifi_sync" + private let bcgSchedulerTaskId = "co.electriccoin.scheduler" + private var monitor: NWPathMonitor? + private let workerQueue = DispatchQueue(label: "Monitor") + private var isConnectedToWifi = false + + let rootStore = RootStore( + initialState: .initial + ) { + RootReducer( + tokenName: TargetConstants.tokenName, + zcashNetwork: TargetConstants.zcashNetwork + ).logging() + } + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { +#if DEBUG + // Short-circuit if running unit tests to avoid side-effects from the app running. + guard !_XCTIsTesting else { return true } +#endif + + walletLogger = OSLogger(logLevel: .debug, category: LoggerConstants.walletLogs) + + handleBackgroundTask() + + return true + } + + func application( + _ application: UIApplication, + shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier + ) -> Bool { + return extensionPointIdentifier != UIApplication.ExtensionPointIdentifier.keyboard + } +} + +// MARK: - BackgroundTasks + +extension AppDelegate { + private func handleBackgroundTask() { + // We require the background task to run when connected to the power and wifi + monitor = NWPathMonitor(requiredInterfaceType: .wifi) + monitor?.pathUpdateHandler = { [weak self] path in + if path.status == .satisfied { + self?.isConnectedToWifi = true + } else { + self?.isConnectedToWifi = false + } + LoggerProxy.event("BGTask isConnectedToWifi \(path.status == .satisfied)") + } + monitor?.start(queue: workerQueue) + + // set the default behavior for the NSDecimalNumber + NSDecimalNumber.defaultBehavior = Zatoshi.decimalHandler + rootStore.send(.initialization(.appDelegate(.didFinishLaunching))) + + registerTasks() + } + + private func registerTasks() { + let bcgSyncTaskResult = BGTaskScheduler.shared.register( + forTaskWithIdentifier: bcgTaskId, + using: DispatchQueue.main + ) { [self] task in + LoggerProxy.event("BGTask BGTaskScheduler.shared.register SYNC called") + guard let task = task as? BGProcessingTask else { + return + } + + startBackgroundTask(task) + } + + LoggerProxy.event("BGTask SYNC registered \(bcgSyncTaskResult)") + + let bcgSchedulerTaskResult = BGTaskScheduler.shared.register( + forTaskWithIdentifier: bcgSchedulerTaskId, + using: DispatchQueue.main + ) { [self] task in + LoggerProxy.event("BGTask BGTaskScheduler.shared.register SCHEDULER called") + guard let task = task as? BGProcessingTask else { + return + } + + scheduleSchedulerBackgroundTask() + scheduleBackgroundTask() + + task.setTaskCompleted(success: true) + } + + LoggerProxy.event("BGTask SCHEDULER registered \(bcgSchedulerTaskResult)") + } + + private func startBackgroundTask(_ task: BGProcessingTask) { + LoggerProxy.event("BGTask startBackgroundTask called") + + // schedule tasks for the next time + scheduleBackgroundTask() + scheduleSchedulerBackgroundTask() + + guard isConnectedToWifi else { + LoggerProxy.event("BGTask startBackgroundTask: not connected to the wifi") + task.setTaskCompleted(success: false) + return + } + + // start the syncing + rootStore.send(.initialization(.appDelegate(.backgroundTask(task)))) + + task.expirationHandler = { [rootStore] in + LoggerProxy.event("BGTask startBackgroundTask expirationHandler called") + // stop the syncing because the allocated time is about to expire + rootStore.send(.initialization(.appDelegate(.didEnterBackground))) + } + } + + func scheduleBackgroundTask() { + // This method can be called as many times as needed, the previously submitted + // request will be overridden by the new one. + LoggerProxy.event("BGTask scheduleBackgroundTask called") + + let request = BGProcessingTaskRequest(identifier: bcgTaskId) + + let today = Calendar.current.startOfDay(for: .now) + guard let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today) else { + LoggerProxy.event("BGTask scheduleBackgroundTask failed to schedule time") + return + } + + let earlyMorningComponent = DateComponents(hour: 3, minute: Int.random(in: 0...60)) + let earlyMorning = Calendar.current.date(byAdding: earlyMorningComponent, to: tomorrow) + request.earliestBeginDate = earlyMorning + request.requiresExternalPower = true + request.requiresNetworkConnectivity = true + + do { + try BGTaskScheduler.shared.submit(request) + LoggerProxy.event("BGTask scheduleBackgroundTask succeeded to submit") + } catch { + LoggerProxy.event("BGTask scheduleBackgroundTask failed to submit, error: \(error)") + } + } + + func scheduleSchedulerBackgroundTask() { + // This method can be called as many times as needed, the previously submitted + // request will be overridden by the new one. + LoggerProxy.event("BGTask scheduleSchedulerBackgroundTask called") + + let request = BGProcessingTaskRequest(identifier: bcgSchedulerTaskId) + + let today = Calendar.current.startOfDay(for: .now) + guard let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today) else { + LoggerProxy.event("BGTask scheduleSchedulerBackgroundTask failed to schedule time") + return + } + + let afternoonComponent = DateComponents(hour: 14, minute: Int.random(in: 0...60)) + let afternoon = Calendar.current.date(byAdding: afternoonComponent, to: tomorrow) + request.earliestBeginDate = afternoon + request.requiresExternalPower = false + request.requiresNetworkConnectivity = false + + do { + try BGTaskScheduler.shared.submit(request) + LoggerProxy.event("BGTask scheduleSchedulerBackgroundTask succeeded to submit") + } catch { + LoggerProxy.event("BGTask scheduleSchedulerBackgroundTask failed to submit, error: \(error)") + } + } +} diff --git a/secant/SecantApp.swift b/secant/SecantApp.swift index 700c557..6546581 100644 --- a/secant/SecantApp.swift +++ b/secant/SecantApp.swift @@ -12,42 +12,6 @@ import ZcashLightClientKit import SDKSynchronizer import Utils import Root -import XCTestDynamicOverlay - -final class AppDelegate: NSObject, UIApplicationDelegate { - let rootStore = RootStore( - initialState: .initial - ) { - RootReducer( - tokenName: TargetConstants.tokenName, - zcashNetwork: TargetConstants.zcashNetwork - ).logging() - } - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { -#if DEBUG - // Short-circuit if running unit tests to avoid side-effects from the app running. - guard !_XCTIsTesting else { return true } -#endif - - walletLogger = OSLogger(logLevel: .debug, category: LoggerConstants.walletLogs) - - // set the default behavior for the NSDecimalNumber - NSDecimalNumber.defaultBehavior = Zatoshi.decimalHandler - rootStore.send(.initialization(.appDelegate(.didFinishLaunching))) - return true - } - - func application( - _ application: UIApplication, - shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier - ) -> Bool { - return extensionPointIdentifier != UIApplication.ExtensionPointIdentifier.keyboard - } -} @main struct SecantApp: App { @@ -74,6 +38,8 @@ struct SecantApp: App { } .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in appDelegate.rootStore.send(.initialization(.appDelegate(.didEnterBackground))) + appDelegate.scheduleBackgroundTask() + appDelegate.scheduleSchedulerBackgroundTask() } .preferredColorScheme(.light) } diff --git a/secant/secant-mainnet-Info.plist b/secant/secant-mainnet-Info.plist index 813991b..528b672 100644 --- a/secant/secant-mainnet-Info.plist +++ b/secant/secant-mainnet-Info.plist @@ -4,8 +4,8 @@ BGTaskSchedulerPermittedIdentifiers - co.electriccoin.backgroundProcessingTask - co.electriccoin.backgroundAppRefreshTask + co.electriccoin.power_wifi_sync + co.electriccoin.scheduler CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) @@ -46,11 +46,6 @@ Scan zAddress Qr Codes NSFaceIDUsageDescription To access sensitive wallet data which should never be shared with anyone. For example backup phrase seed that can restore the wallet and gain access to funds. - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UIAppFonts Zboto.otf @@ -91,6 +86,11 @@ Archivo-ExtraLightItalic.otf Archivo-ExtraLight.otf + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/secant/secant-testnet-Info.plist b/secant/secant-testnet-Info.plist index 5ae90f7..8ce809d 100644 --- a/secant/secant-testnet-Info.plist +++ b/secant/secant-testnet-Info.plist @@ -4,8 +4,8 @@ BGTaskSchedulerPermittedIdentifiers - co.electriccoin.backgroundProcessingTask - co.electriccoin.backgroundAppRefreshTask + co.electriccoin.power_wifi_sync + co.electriccoin.scheduler CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) diff --git a/secantTests/RootTests/AppInitializationTests.swift b/secantTests/RootTests/AppInitializationTests.swift index d50241d..4076b9f 100644 --- a/secantTests/RootTests/AppInitializationTests.swift +++ b/secantTests/RootTests/AppInitializationTests.swift @@ -82,6 +82,8 @@ class AppInitializationTests: XCTestCase { await store.receive(.initialization(.initializationSuccessfullyDone(nil))) + await store.receive(.initialization(.registerForSynchronizersUpdate)) + await store.receive(.destination(.updateDestination(.tabs))) { state in state.destinationState.previousDestination = .welcome state.destinationState.internalDestination = .tabs @@ -158,6 +160,8 @@ class AppInitializationTests: XCTestCase { await store.receive(.initialization(.initializationSuccessfullyDone(nil))) + await store.receive(.initialization(.registerForSynchronizersUpdate)) + await store.receive(.destination(.updateDestination(.phraseDisplay))) { state in state.destinationState.previousDestination = .welcome state.destinationState.internalDestination = .phraseDisplay diff --git a/secantTests/RootTests/RootTests.swift b/secantTests/RootTests/RootTests.swift index 74ffca2..99d2d7d 100644 --- a/secantTests/RootTests/RootTests.swift +++ b/secantTests/RootTests/RootTests.swift @@ -208,15 +208,20 @@ class RootTests: XCTestCase { RootReducer(tokenName: "ZEC", zcashNetwork: ZcashNetworkBuilder.network(for: .testnet)) } + store.dependencies.mainQueue = .immediate + store.dependencies.sdkSynchronizer = .noOp + // swiftlint:disable line_length let uAddress = try UnifiedAddress( encoding: "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h", network: .testnet ) - + await store.send(.initialization(.initializationSuccessfullyDone(uAddress))) { state in state.tabsState.addressDetailsState.uAddress = uAddress } - + + await store.receive(.initialization(.registerForSynchronizersUpdate)) + await store.finish() } }