mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 00:42:29 -06:00
[PM-26063] Add flight recorder to Authenticator's settings (#2147)
This commit is contained in:
parent
b8161ee72c
commit
26c158b2e5
@ -56,3 +56,17 @@ extension DefaultAppModule: AppModule {
|
|||||||
).asAnyCoordinator()
|
).asAnyCoordinator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - DefaultAppModule + FlightRecorderModule
|
||||||
|
|
||||||
|
extension DefaultAppModule: FlightRecorderModule {
|
||||||
|
public func makeFlightRecorderCoordinator(
|
||||||
|
stackNavigator: StackNavigator,
|
||||||
|
) -> AnyCoordinator<FlightRecorderRoute, Void> {
|
||||||
|
FlightRecorderCoordinator(
|
||||||
|
services: services,
|
||||||
|
stackNavigator: stackNavigator,
|
||||||
|
)
|
||||||
|
.asAnyCoordinator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,9 @@ enum SettingsAction: Equatable {
|
|||||||
/// The export items button was tapped.
|
/// The export items button was tapped.
|
||||||
case exportItemsTapped
|
case exportItemsTapped
|
||||||
|
|
||||||
|
/// An action for the Flight Recorder feature.
|
||||||
|
case flightRecorder(FlightRecorderSettingsSectionAction)
|
||||||
|
|
||||||
/// The help center button was tapped.
|
/// The help center button was tapped.
|
||||||
case helpCenterTapped
|
case helpCenterTapped
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,18 @@ import BitwardenKit
|
|||||||
|
|
||||||
/// Effects that can be processed by an `SettingsProcessor`.
|
/// Effects that can be processed by an `SettingsProcessor`.
|
||||||
enum SettingsEffect: Equatable {
|
enum SettingsEffect: Equatable {
|
||||||
|
/// An effect for the flight recorder feature.
|
||||||
|
case flightRecorder(FlightRecorderSettingsSectionEffect)
|
||||||
|
|
||||||
/// The view appeared so the initial data should be loaded.
|
/// The view appeared so the initial data should be loaded.
|
||||||
case loadData
|
case loadData
|
||||||
|
|
||||||
/// The session timeout value was changed.
|
/// The session timeout value was changed.
|
||||||
case sessionTimeoutValueChanged(SessionTimeoutValue)
|
case sessionTimeoutValueChanged(SessionTimeoutValue)
|
||||||
|
|
||||||
|
/// Stream the active flight recorder log.
|
||||||
|
case streamFlightRecorderLog
|
||||||
|
|
||||||
/// Unlock with Biometrics was toggled.
|
/// Unlock with Biometrics was toggled.
|
||||||
case toggleUnlockWithBiometrics(Bool)
|
case toggleUnlockWithBiometrics(Bool)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
|
|||||||
& HasConfigService
|
& HasConfigService
|
||||||
& HasErrorReporter
|
& HasErrorReporter
|
||||||
& HasExportItemsService
|
& HasExportItemsService
|
||||||
|
& HasFlightRecorder
|
||||||
& HasPasteboardService
|
& HasPasteboardService
|
||||||
& HasStateService
|
& HasStateService
|
||||||
|
|
||||||
@ -51,6 +52,15 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
|
|||||||
|
|
||||||
override func perform(_ effect: SettingsEffect) async {
|
override func perform(_ effect: SettingsEffect) async {
|
||||||
switch effect {
|
switch effect {
|
||||||
|
case let .flightRecorder(flightRecorderEffect):
|
||||||
|
switch flightRecorderEffect {
|
||||||
|
case let .toggleFlightRecorder(isOn):
|
||||||
|
if isOn {
|
||||||
|
coordinator.navigate(to: .flightRecorder(.enableFlightRecorder))
|
||||||
|
} else {
|
||||||
|
await services.flightRecorder.disableFlightRecorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
case .loadData:
|
case .loadData:
|
||||||
await loadData()
|
await loadData()
|
||||||
case let .sessionTimeoutValueChanged(timeoutValue):
|
case let .sessionTimeoutValueChanged(timeoutValue):
|
||||||
@ -65,6 +75,8 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
|
|||||||
minutes: timeoutValue.rawValue,
|
minutes: timeoutValue.rawValue,
|
||||||
userId: services.appSettingsStore.localUserId,
|
userId: services.appSettingsStore.localUserId,
|
||||||
)
|
)
|
||||||
|
case .streamFlightRecorderLog:
|
||||||
|
await streamFlightRecorderLog()
|
||||||
case let .toggleUnlockWithBiometrics(isOn):
|
case let .toggleUnlockWithBiometrics(isOn):
|
||||||
await setBiometricAuth(isOn)
|
await setBiometricAuth(isOn)
|
||||||
}
|
}
|
||||||
@ -88,6 +100,11 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
|
|||||||
services.appSettingsStore.defaultSaveOption = option
|
services.appSettingsStore.defaultSaveOption = option
|
||||||
case .exportItemsTapped:
|
case .exportItemsTapped:
|
||||||
coordinator.navigate(to: .exportItems)
|
coordinator.navigate(to: .exportItems)
|
||||||
|
case let .flightRecorder(flightRecorderAction):
|
||||||
|
switch flightRecorderAction {
|
||||||
|
case .viewLogsTapped:
|
||||||
|
coordinator.navigate(to: .flightRecorder(.flightRecorderLogs))
|
||||||
|
}
|
||||||
case .helpCenterTapped:
|
case .helpCenterTapped:
|
||||||
state.url = ExternalLinksConstants.helpAndFeedback
|
state.url = ExternalLinksConstants.helpAndFeedback
|
||||||
case .importItemsTapped:
|
case .importItemsTapped:
|
||||||
@ -189,6 +206,13 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
|
|||||||
services.errorReporter.log(error: error)
|
services.errorReporter.log(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Streams the flight recorder's active log metadata.
|
||||||
|
private func streamFlightRecorderLog() async {
|
||||||
|
for await activeLog in await services.flightRecorder.activeLogPublisher().values {
|
||||||
|
state.flightRecorderState.activeLog = activeLog
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SelectLanguageDelegate
|
// MARK: - SelectLanguageDelegate
|
||||||
|
|||||||
@ -14,6 +14,7 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
var biometricsRepository: MockBiometricsRepository!
|
var biometricsRepository: MockBiometricsRepository!
|
||||||
var configService: MockConfigService!
|
var configService: MockConfigService!
|
||||||
var coordinator: MockCoordinator<SettingsRoute, SettingsEvent>!
|
var coordinator: MockCoordinator<SettingsRoute, SettingsEvent>!
|
||||||
|
var flightRecorder: MockFlightRecorder!
|
||||||
var pasteboardService: MockPasteboardService!
|
var pasteboardService: MockPasteboardService!
|
||||||
var subject: SettingsProcessor!
|
var subject: SettingsProcessor!
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
biometricsRepository = MockBiometricsRepository()
|
biometricsRepository = MockBiometricsRepository()
|
||||||
configService = MockConfigService()
|
configService = MockConfigService()
|
||||||
coordinator = MockCoordinator()
|
coordinator = MockCoordinator()
|
||||||
|
flightRecorder = MockFlightRecorder()
|
||||||
pasteboardService = MockPasteboardService()
|
pasteboardService = MockPasteboardService()
|
||||||
subject = SettingsProcessor(
|
subject = SettingsProcessor(
|
||||||
coordinator: coordinator.asAnyCoordinator(),
|
coordinator: coordinator.asAnyCoordinator(),
|
||||||
@ -37,6 +39,7 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
authenticatorItemRepository: authItemRepository,
|
authenticatorItemRepository: authItemRepository,
|
||||||
biometricsRepository: biometricsRepository,
|
biometricsRepository: biometricsRepository,
|
||||||
configService: configService,
|
configService: configService,
|
||||||
|
flightRecorder: flightRecorder,
|
||||||
pasteboardService: pasteboardService,
|
pasteboardService: pasteboardService,
|
||||||
),
|
),
|
||||||
state: SettingsState(),
|
state: SettingsState(),
|
||||||
@ -52,12 +55,38 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
biometricsRepository = nil
|
biometricsRepository = nil
|
||||||
configService = nil
|
configService = nil
|
||||||
coordinator = nil
|
coordinator = nil
|
||||||
|
flightRecorder = nil
|
||||||
pasteboardService = nil
|
pasteboardService = nil
|
||||||
subject = nil
|
subject = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Tests
|
// MARK: Tests
|
||||||
|
|
||||||
|
/// `perform(_:)` with `.flightRecorder(.toggleFlightRecorder(true))` navigates to the enable
|
||||||
|
/// flight recorder screen when toggled on.
|
||||||
|
@MainActor
|
||||||
|
func test_perform_flightRecorder_toggleFlightRecorder_on() async {
|
||||||
|
XCTAssertNil(subject.state.flightRecorderState.activeLog)
|
||||||
|
|
||||||
|
await subject.perform(.flightRecorder(.toggleFlightRecorder(true)))
|
||||||
|
|
||||||
|
XCTAssertEqual(coordinator.routes, [.flightRecorder(.enableFlightRecorder)])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `perform(_:)` with `.flightRecorder(.toggleFlightRecorder(false))` disables the flight
|
||||||
|
/// recorder when toggled off.
|
||||||
|
@MainActor
|
||||||
|
func test_perform_flightRecorder_toggleFlightRecorder_off() async throws {
|
||||||
|
subject.state.flightRecorderState.activeLog = FlightRecorderData.LogMetadata(
|
||||||
|
duration: .eightHours,
|
||||||
|
startDate: .now,
|
||||||
|
)
|
||||||
|
|
||||||
|
await subject.perform(.flightRecorder(.toggleFlightRecorder(false)))
|
||||||
|
|
||||||
|
XCTAssertTrue(flightRecorder.disableFlightRecorderCalled)
|
||||||
|
}
|
||||||
|
|
||||||
/// Performing `.loadData` sets the 'defaultSaveOption' to the current value in 'AppSettingsStore'.
|
/// Performing `.loadData` sets the 'defaultSaveOption' to the current value in 'AppSettingsStore'.
|
||||||
@MainActor
|
@MainActor
|
||||||
func test_perform_loadData_defaultSaveOption() async throws {
|
func test_perform_loadData_defaultSaveOption() async throws {
|
||||||
@ -157,6 +186,26 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
XCTAssertEqual(subject.state.sessionTimeoutValue, .fifteenMinutes)
|
XCTAssertEqual(subject.state.sessionTimeoutValue, .fifteenMinutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `perform(_:)` with `.streamFlightRecorderLog` subscribes to the active flight recorder log.
|
||||||
|
@MainActor
|
||||||
|
func test_perform_streamFlightRecorderLog() async throws {
|
||||||
|
XCTAssertNil(subject.state.flightRecorderState.activeLog)
|
||||||
|
|
||||||
|
let task = Task {
|
||||||
|
await subject.perform(.streamFlightRecorderLog)
|
||||||
|
}
|
||||||
|
defer { task.cancel() }
|
||||||
|
|
||||||
|
let log = FlightRecorderData.LogMetadata(duration: .eightHours, startDate: .now)
|
||||||
|
flightRecorder.activeLogSubject.send(log)
|
||||||
|
try await waitForAsync { self.subject.state.flightRecorderState.activeLog != nil }
|
||||||
|
XCTAssertEqual(subject.state.flightRecorderState.activeLog, log)
|
||||||
|
|
||||||
|
flightRecorder.activeLogSubject.send(nil)
|
||||||
|
try await waitForAsync { self.subject.state.flightRecorderState.activeLog == nil }
|
||||||
|
XCTAssertNil(subject.state.flightRecorderState.activeLog)
|
||||||
|
}
|
||||||
|
|
||||||
/// Performing `.toggleUnlockWithBiometrics` with a `false` value disables biometric unlock and resets the
|
/// Performing `.toggleUnlockWithBiometrics` with a `false` value disables biometric unlock and resets the
|
||||||
/// session timeout to `.never`
|
/// session timeout to `.never`
|
||||||
@MainActor
|
@MainActor
|
||||||
@ -216,6 +265,15 @@ class SettingsProcessorTests: BitwardenTestCase {
|
|||||||
XCTAssertEqual(coordinator.routes.last, .exportItems)
|
XCTAssertEqual(coordinator.routes.last, .exportItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `receive(_:)` with action `.flightRecorder(.viewLogsTapped)` navigates to the view flight
|
||||||
|
/// recorder logs screen.
|
||||||
|
@MainActor
|
||||||
|
func test_receive_flightRecorder_viewFlightRecorderLogsTapped() {
|
||||||
|
subject.receive(.flightRecorder(.viewLogsTapped))
|
||||||
|
|
||||||
|
XCTAssertEqual(coordinator.routes, [.flightRecorder(.flightRecorderLogs)])
|
||||||
|
}
|
||||||
|
|
||||||
/// Receiving `.syncWithBitwardenAppTapped` adds the Password Manager settings URL to the state to
|
/// Receiving `.syncWithBitwardenAppTapped` adds the Password Manager settings URL to the state to
|
||||||
/// navigate the user to the BWPM app's settings.
|
/// navigate the user to the BWPM app's settings.
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
@ -22,6 +22,9 @@ struct SettingsState: Equatable {
|
|||||||
/// The current default save option.
|
/// The current default save option.
|
||||||
var defaultSaveOption: DefaultSaveOption = .none
|
var defaultSaveOption: DefaultSaveOption = .none
|
||||||
|
|
||||||
|
/// The state for the Flight Recorder feature.
|
||||||
|
var flightRecorderState = FlightRecorderSettingsSectionState()
|
||||||
|
|
||||||
/// The current default save option.
|
/// The current default save option.
|
||||||
var sessionTimeoutValue: SessionTimeoutValue = .never
|
var sessionTimeoutValue: SessionTimeoutValue = .never
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,34 @@ class SettingsViewTests: BitwardenTestCase {
|
|||||||
XCTAssertEqual(processor.dispatchedActions.last, .exportItemsTapped)
|
XCTAssertEqual(processor.dispatchedActions.last, .exportItemsTapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The flight recorder toggle turns logging on and off.
|
||||||
|
@MainActor
|
||||||
|
func test_flightRecorder_toggle_tap() async throws {
|
||||||
|
let toggle = try subject.inspect().find(toggleWithAccessibilityLabel: Localizations.flightRecorder)
|
||||||
|
|
||||||
|
try toggle.tap()
|
||||||
|
try await waitForAsync { !self.processor.effects.isEmpty }
|
||||||
|
XCTAssertEqual(processor.effects, [.flightRecorder(.toggleFlightRecorder(true))])
|
||||||
|
processor.effects.removeAll()
|
||||||
|
|
||||||
|
processor.state.flightRecorderState.activeLog = FlightRecorderData.LogMetadata(
|
||||||
|
duration: .eightHours,
|
||||||
|
startDate: .now,
|
||||||
|
)
|
||||||
|
try toggle.tap()
|
||||||
|
try await waitForAsync { !self.processor.effects.isEmpty }
|
||||||
|
XCTAssertEqual(processor.effects, [.flightRecorder(.toggleFlightRecorder(false))])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tapping the flight recorder view recorded logs button dispatches the
|
||||||
|
/// `.viewFlightRecorderLogsTapped` action.
|
||||||
|
@MainActor
|
||||||
|
func test_flightRecorder_viewRecordedLogsButton_tap() throws {
|
||||||
|
let button = try subject.inspect().find(button: Localizations.viewRecordedLogs)
|
||||||
|
try button.tap()
|
||||||
|
XCTAssertEqual(processor.dispatchedActions.last, .flightRecorder(.viewLogsTapped))
|
||||||
|
}
|
||||||
|
|
||||||
/// Tapping the help center button dispatches the `.helpCenterTapped` action.
|
/// Tapping the help center button dispatches the `.helpCenterTapped` action.
|
||||||
@MainActor
|
@MainActor
|
||||||
func test_helpCenterButton_tap() throws {
|
func test_helpCenterButton_tap() throws {
|
||||||
|
|||||||
@ -49,13 +49,24 @@ struct SettingsView: View {
|
|||||||
.task {
|
.task {
|
||||||
await store.perform(.loadData)
|
await store.perform(.loadData)
|
||||||
}
|
}
|
||||||
|
.task {
|
||||||
|
await store.perform(.streamFlightRecorderLog)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private views
|
// MARK: Private views
|
||||||
|
|
||||||
/// The about section containing privacy policy and version information.
|
/// The about section containing privacy policy and version information.
|
||||||
@ViewBuilder private var aboutSection: some View {
|
@ViewBuilder private var aboutSection: some View {
|
||||||
SectionView(Localizations.about) {
|
SectionView(Localizations.about, contentSpacing: 8) {
|
||||||
|
FlightRecorderSettingsSectionView(
|
||||||
|
store: store.child(
|
||||||
|
state: \.flightRecorderState,
|
||||||
|
mapAction: { .flightRecorder($0) },
|
||||||
|
mapEffect: { .flightRecorder($0) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
ContentBlock(dividerLeadingPadding: 16) {
|
ContentBlock(dividerLeadingPadding: 16) {
|
||||||
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)
|
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator {
|
|||||||
|
|
||||||
/// The module types required by this coordinator for creating child coordinators.
|
/// The module types required by this coordinator for creating child coordinators.
|
||||||
typealias Module = FileSelectionModule
|
typealias Module = FileSelectionModule
|
||||||
|
& FlightRecorderModule
|
||||||
& TutorialModule
|
& TutorialModule
|
||||||
|
|
||||||
typealias Services = HasAppInfoService
|
typealias Services = HasAppInfoService
|
||||||
@ -23,6 +24,7 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator {
|
|||||||
& HasErrorAlertServices.ErrorAlertServices
|
& HasErrorAlertServices.ErrorAlertServices
|
||||||
& HasErrorReporter
|
& HasErrorReporter
|
||||||
& HasExportItemsService
|
& HasExportItemsService
|
||||||
|
& HasFlightRecorder
|
||||||
& HasImportItemsService
|
& HasImportItemsService
|
||||||
& HasPasteboardService
|
& HasPasteboardService
|
||||||
& HasStateService
|
& HasStateService
|
||||||
@ -81,6 +83,8 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator {
|
|||||||
stackNavigator?.dismiss()
|
stackNavigator?.dismiss()
|
||||||
case .exportItems:
|
case .exportItems:
|
||||||
showExportItems()
|
showExportItems()
|
||||||
|
case let .flightRecorder(flightRecorderRoute):
|
||||||
|
showFlightRecorder(route: flightRecorderRoute)
|
||||||
case .importItems:
|
case .importItems:
|
||||||
showImportItems()
|
showImportItems()
|
||||||
case let .importItemsFileSelection(route):
|
case let .importItemsFileSelection(route):
|
||||||
@ -132,6 +136,17 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator {
|
|||||||
stackNavigator?.present(navController)
|
stackNavigator?.present(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a flight recorder view.
|
||||||
|
///
|
||||||
|
/// - Parameter route: A `FlightRecorderRoute` to navigate to.
|
||||||
|
///
|
||||||
|
private func showFlightRecorder(route: FlightRecorderRoute) {
|
||||||
|
guard let stackNavigator else { return }
|
||||||
|
let coordinator = module.makeFlightRecorderCoordinator(stackNavigator: stackNavigator)
|
||||||
|
coordinator.start()
|
||||||
|
coordinator.navigate(to: route)
|
||||||
|
}
|
||||||
|
|
||||||
/// Presents an activity controller for importing items.
|
/// Presents an activity controller for importing items.
|
||||||
///
|
///
|
||||||
private func showImportItems() {
|
private func showImportItems() {
|
||||||
|
|||||||
@ -69,6 +69,16 @@ class SettingsCoordinatorTests: BitwardenTestCase {
|
|||||||
XCTAssertTrue(navigationController.viewControllers.first is UIHostingController<ExportItemsView>)
|
XCTAssertTrue(navigationController.viewControllers.first is UIHostingController<ExportItemsView>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `navigate(to:)` with `.flightRecorder` starts flight recorder coordinator and navigates to
|
||||||
|
/// the enable flight recorder view.
|
||||||
|
@MainActor
|
||||||
|
func test_navigateTo_flightRecorder() throws {
|
||||||
|
subject.navigate(to: .flightRecorder(.enableFlightRecorder))
|
||||||
|
|
||||||
|
XCTAssertTrue(module.flightRecorderCoordinator.isStarted)
|
||||||
|
XCTAssertEqual(module.flightRecorderCoordinator.routes.last, .enableFlightRecorder)
|
||||||
|
}
|
||||||
|
|
||||||
/// `navigate(to:)` with `.selectLanguage()` presents the select language view.
|
/// `navigate(to:)` with `.selectLanguage()` presents the select language view.
|
||||||
@MainActor
|
@MainActor
|
||||||
func test_navigateTo_selectLanguage() throws {
|
func test_navigateTo_selectLanguage() throws {
|
||||||
|
|||||||
@ -11,6 +11,9 @@ public enum SettingsRoute: Equatable, Hashable {
|
|||||||
/// A route to the export items view.
|
/// A route to the export items view.
|
||||||
case exportItems
|
case exportItems
|
||||||
|
|
||||||
|
/// A route to a Flight Recorder view.
|
||||||
|
case flightRecorder(FlightRecorderRoute)
|
||||||
|
|
||||||
/// A route to the import items view.
|
/// A route to the import items view.
|
||||||
case importItems
|
case importItems
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ class MockAppModule:
|
|||||||
AuthModule,
|
AuthModule,
|
||||||
DebugMenuModule,
|
DebugMenuModule,
|
||||||
FileSelectionModule,
|
FileSelectionModule,
|
||||||
|
FlightRecorderModule,
|
||||||
ItemListModule,
|
ItemListModule,
|
||||||
TutorialModule,
|
TutorialModule,
|
||||||
TabModule {
|
TabModule {
|
||||||
@ -18,6 +19,7 @@ class MockAppModule:
|
|||||||
var debugMenuCoordinator = MockCoordinator<DebugMenuRoute, Void>()
|
var debugMenuCoordinator = MockCoordinator<DebugMenuRoute, Void>()
|
||||||
var fileSelectionDelegate: FileSelectionDelegate?
|
var fileSelectionDelegate: FileSelectionDelegate?
|
||||||
var fileSelectionCoordinator = MockCoordinator<FileSelectionRoute, FileSelectionEvent>()
|
var fileSelectionCoordinator = MockCoordinator<FileSelectionRoute, FileSelectionEvent>()
|
||||||
|
var flightRecorderCoordinator = MockCoordinator<FlightRecorderRoute, Void>()
|
||||||
var itemListCoordinator = MockCoordinator<ItemListRoute, ItemListEvent>()
|
var itemListCoordinator = MockCoordinator<ItemListRoute, ItemListEvent>()
|
||||||
var tabCoordinator = MockCoordinator<TabRoute, Void>()
|
var tabCoordinator = MockCoordinator<TabRoute, Void>()
|
||||||
var tutorialCoordinator = MockCoordinator<TutorialRoute, TutorialEvent>()
|
var tutorialCoordinator = MockCoordinator<TutorialRoute, TutorialEvent>()
|
||||||
@ -55,6 +57,12 @@ class MockAppModule:
|
|||||||
return fileSelectionCoordinator.asAnyCoordinator()
|
return fileSelectionCoordinator.asAnyCoordinator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeFlightRecorderCoordinator(
|
||||||
|
stackNavigator _: StackNavigator,
|
||||||
|
) -> AnyCoordinator<FlightRecorderRoute, Void> {
|
||||||
|
flightRecorderCoordinator.asAnyCoordinator()
|
||||||
|
}
|
||||||
|
|
||||||
func makeItemListCoordinator(
|
func makeItemListCoordinator(
|
||||||
stackNavigator _: StackNavigator,
|
stackNavigator _: StackNavigator,
|
||||||
) -> AnyCoordinator<ItemListRoute, ItemListEvent> {
|
) -> AnyCoordinator<ItemListRoute, ItemListEvent> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user