diff --git a/RadixWallet/Features/DappsAndPersonas/Personas/Child/List/PersonaList+Reducer.swift b/RadixWallet/Features/DappsAndPersonas/Personas/Child/List/PersonaList+Reducer.swift index 30bad61a3c..f5f3b64b0c 100644 --- a/RadixWallet/Features/DappsAndPersonas/Personas/Child/List/PersonaList+Reducer.swift +++ b/RadixWallet/Features/DappsAndPersonas/Personas/Child/List/PersonaList+Reducer.swift @@ -50,7 +50,7 @@ public struct PersonaList: Sendable, FeatureReducer { } public enum InternalAction: Sendable, Equatable { - case personasLoaded(IdentifiedArrayOf) + case setPersonas([Persona]) case setSecurityProblems([SecurityProblem]) } @@ -80,27 +80,12 @@ public struct PersonaList: Sendable, FeatureReducer { case personasMissingFromClient(OrderedSet) } - /// Returns the ids of personas to include under the given strategy. nil means that all ids should be included - private func personaIDs(_ strategy: State.ReloadingStrategy) async throws -> OrderedSet? { - switch strategy { - case .all: - return nil - case let .ids(ids): - return ids - case let .dApp(dAppID): - guard let dApp = try? await authorizedDappsClient.getDetailedDapp(dAppID) else { return [] } - return OrderedSet(dApp.detailedAuthorizedPersonas.map(\.id)) - } - } - public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { switch internalAction { - case var .personasLoaded(personas): - personas.mutateAll { persona in - persona.securityProblemsConfig.update(problems: state.problems) - } - + case let .setPersonas(personas): state.personas = personas + .map { PersonaFeature.State(persona: $0, problems: state.problems) } + .asIdentified() return .none case let .setSecurityProblems(problems): @@ -133,15 +118,15 @@ public struct PersonaList: Sendable, FeatureReducer { } private func personasEffect(state: State) -> Effect { - .run { [strategy = state.strategy, problems = state.problems] send in + .run { [strategy = state.strategy] send in for try await personas in await personasClient.personas() { guard !Task.isCancelled else { return } let ids = try await personaIDs(strategy) ?? OrderedSet(validating: personas.ids) - let result = ids.compactMap { personas[id: $0] }.map { PersonaFeature.State(persona: $0, problems: problems) } + let result = ids.compactMap { personas[id: $0] } guard result.count == ids.count else { throw UpdatePersonaError.personasMissingFromClient(ids.subtracting(result.map(\.id))) } - await send(.internal(.personasLoaded(result.asIdentified()))) + await send(.internal(.setPersonas(result))) } } catch: { error, _ in loggerGlobal.error("Failed to update personas from client, error: \(error)") @@ -157,4 +142,17 @@ public struct PersonaList: Sendable, FeatureReducer { } } } + + /// Returns the ids of personas to include under the given strategy. nil means that all ids should be included + private func personaIDs(_ strategy: State.ReloadingStrategy) async throws -> OrderedSet? { + switch strategy { + case .all: + return nil + case let .ids(ids): + return ids + case let .dApp(dAppID): + guard let dApp = try? await authorizedDappsClient.getDetailedDapp(dAppID) else { return [] } + return OrderedSet(dApp.detailedAuthorizedPersonas.map(\.id)) + } + } } diff --git a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator+View.swift b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator+View.swift index ff4464dda4..1007e2f34a 100644 --- a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator+View.swift +++ b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator+View.swift @@ -12,34 +12,36 @@ extension RestoreProfileFromBackupCoordinator { } public var body: some SwiftUI.View { - NavigationStackStore( - store.scope(state: \.path, action: { .child(.path($0)) }) - ) { - path(for: store.scope(state: \.root, action: { .child(.root($0)) })) - } destination: { - path(for: $0) - } + SelectBackup.View(store: store.selectBackup) + .inNavigationStack + .destinations(with: store) } + } +} + +private extension StoreOf { + var selectBackup: StoreOf { + scope(state: \.selectBackup, action: \.child.selectBackup) + } + + var destination: PresentationStoreOf { + func scopeState(state: State) -> PresentationState { + state.$destination + } + return scope(state: scopeState, action: Action.destination) + } +} + +@MainActor +private extension View { + func destinations(with store: StoreOf) -> some View { + let destinationStore = store.destination + return importMnemonics(with: destinationStore) + } - private func path( - for store: StoreOf - ) -> some SwiftUI.View { - SwitchStore(store) { state in - switch state { - case .selectBackup: - CaseLet( - /RestoreProfileFromBackupCoordinator.Path.State.selectBackup, - action: RestoreProfileFromBackupCoordinator.Path.Action.selectBackup, - then: { SelectBackup.View(store: $0) } - ) - case .importMnemonicsFlow: - CaseLet( - /RestoreProfileFromBackupCoordinator.Path.State.importMnemonicsFlow, - action: RestoreProfileFromBackupCoordinator.Path.Action.importMnemonicsFlow, - then: { ImportMnemonicsFlowCoordinator.View(store: $0) } - ) - } - } + private func importMnemonics(with destinationStore: PresentationStoreOf) -> some View { + sheet(store: destinationStore.scope(state: \.importMnemonicsFlow, action: \.importMnemonicsFlow)) { + ImportMnemonicsFlowCoordinator.View(store: $0) } } } diff --git a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator.swift b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator.swift index 2008a972ad..3ae9cae7d8 100644 --- a/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator.swift +++ b/RadixWallet/Features/ProfileBackupsFeature/RestoreProfileFromBackup/Coordinator/RestoreProfileFromBackupCoordinator.swift @@ -11,32 +11,25 @@ public struct ProfileSelection: Sendable, Hashable { // MARK: - RestoreProfileFromBackupCoordinator public struct RestoreProfileFromBackupCoordinator: Sendable, FeatureReducer { public struct State: Sendable, Hashable { - public var root: Path.State - public var path: StackState = .init() + public var selectBackup = SelectBackup.State() public var profileSelection: ProfileSelection? - public init() { - self.root = .selectBackup(.init()) - } + @PresentationState + public var destination: Destination.State? } - public struct Path: Sendable, Hashable, Reducer { + public struct Destination: DestinationReducer { @CasePathable public enum State: Sendable, Hashable { - case selectBackup(SelectBackup.State) case importMnemonicsFlow(ImportMnemonicsFlowCoordinator.State) } @CasePathable public enum Action: Sendable, Equatable { - case selectBackup(SelectBackup.Action) case importMnemonicsFlow(ImportMnemonicsFlowCoordinator.Action) } public var body: some ReducerOf { - Scope(state: \.selectBackup, action: \.selectBackup) { - SelectBackup() - } Scope(state: \.importMnemonicsFlow, action: \.importMnemonicsFlow) { ImportMnemonicsFlowCoordinator() } @@ -44,13 +37,12 @@ public struct RestoreProfileFromBackupCoordinator: Sendable, FeatureReducer { } public enum InternalAction: Sendable, Equatable { - case delayedAppendToPath(RestoreProfileFromBackupCoordinator.Path.State) + case startImportMnemonicsFlow(Profile) } @CasePathable public enum ChildAction: Sendable, Equatable { - case root(Path.Action) - case path(StackActionOf) + case selectBackup(SelectBackup.Action) } public enum DelegateAction: Sendable, Equatable { @@ -69,44 +61,50 @@ public struct RestoreProfileFromBackupCoordinator: Sendable, FeatureReducer { public init() {} public var body: some ReducerOf { - Scope(state: \.root, action: \.child.root) { - Path() + Scope(state: \.selectBackup, action: \.child.selectBackup) { + SelectBackup() } - Reduce(core) - .forEach(\.path, action: \.child.path) { - Path() + .ifLet(destinationPath, action: /Action.destination) { + Destination() } } - public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { - switch internalAction { - case let .delayedAppendToPath(destination): - state.path.append(destination) - return .none - } - } + private let destinationPath: WritableKeyPath> = \.$destination public func reduce(into state: inout State, childAction: ChildAction) -> Effect { switch childAction { - case let .root(.selectBackup(.delegate(.selectedProfile(profile, containsLegacyP2PLinks)))): + case let .selectBackup(.delegate(.selectedProfile(profile, containsLegacyP2PLinks))): state.profileSelection = .init(profile: profile, containsP2PLinks: containsLegacyP2PLinks) return .run { send in try? await clock.sleep(for: .milliseconds(300)) _ = await radixConnectClient.loadP2PLinksAndConnectAll() - await send(.internal(.delayedAppendToPath( - .importMnemonicsFlow(.init(context: .fromOnboarding(profile: profile))) - ))) + await send(.internal(.startImportMnemonicsFlow(profile))) } - case .root(.selectBackup(.delegate(.backToStartOfOnboarding))): + case .selectBackup(.delegate(.backToStartOfOnboarding)): return .send(.delegate(.backToStartOfOnboarding)) - case .root(.selectBackup(.delegate(.profileCreatedFromImportedBDFS))): + case .selectBackup(.delegate(.profileCreatedFromImportedBDFS)): return .send(.delegate(.profileCreatedFromImportedBDFS)) - case let .path(.element(_, action: .importMnemonicsFlow(.delegate(.finishedImportingMnemonics(skipList, _, notYetSavedNewMainBDFS))))): + default: + return .none + } + } + + public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { + switch internalAction { + case let .startImportMnemonicsFlow(profile): + state.destination = .importMnemonicsFlow(.init(context: .fromOnboarding(profile: profile))) + return .none + } + } + + public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { + switch presentedAction { + case let .importMnemonicsFlow(.delegate(.finishedImportingMnemonics(skipList, _, notYetSavedNewMainBDFS))): loggerGlobal.notice("Starting import snapshot process...") guard let profileSelection = state.profileSelection else { preconditionFailure("Expected to have a profile") @@ -130,8 +128,8 @@ public struct RestoreProfileFromBackupCoordinator: Sendable, FeatureReducer { errorQueue.schedule(error) } - case let .path(.element(_, action: .importMnemonicsFlow(.delegate(.finishedEarly(didFail))))): - state.path.removeLast() + case let .importMnemonicsFlow(.delegate(.finishedEarly(didFail))): + state.destination = nil return .run { send in await radixConnectClient.disconnectAll() if didFail {