From a7dc2c4d92b48671c3cf89dd07afded8f1aeceac Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Mon, 11 May 2026 14:08:37 +0100 Subject: [PATCH 1/2] arch: introduce 'navigate' pattern Decouples the navigation contract from the app module. When we move to a multi-module approach, we will not want feature modules depending on other feature modules. Moving navigation (and interface definitions for navigating to screens) to a lower module means we can break cross-module dependencies/circular dependencies. * Destination will be subclassed, and these subclasses define the requirements for types of navigation * Card Browser may take a CardId to scroll to * Exposed publicly by com.ichi2.anki.destinations.navigate(Destination) extensions Issue 20558 Assisted-by: Claude Opus 4.7 --- .../main/java/com/ichi2/anki/AnkiDroidApp.kt | 2 + .../anki/navigation/AnkiDroidNavigator.kt | 27 +++++++++ anki-common/build.gradle.kts | 4 +- .../anki/common/destinations/Destination.kt | 14 +++++ .../anki/common/destinations/Navigator.kt | 58 +++++++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt create mode 100644 anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt create mode 100644 anki-common/src/main/java/com/ichi2/anki/common/destinations/Navigator.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt index 70de4d3f4e4b..343d9b026648 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt @@ -51,6 +51,7 @@ import com.ichi2.anki.logging.FragmentLifecycleLogger import com.ichi2.anki.logging.LogType import com.ichi2.anki.logging.ProductionCrashReportingTree import com.ichi2.anki.logging.RobolectricDebugTree +import com.ichi2.anki.navigation.initializeNavigator import com.ichi2.anki.observability.ChangeManager import com.ichi2.anki.preferences.SharedPreferencesProvider import com.ichi2.anki.preferences.sharedPrefs @@ -136,6 +137,7 @@ open class AnkiDroidApp : ChangeManager.subscribe(this) initializeAcraCrashReporter() + initializeNavigator() val logType = LogType.value when (logType) { LogType.DEBUG -> Timber.plant(DebugTree()) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt b/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt new file mode 100644 index 000000000000..d34d6ce8c3eb --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.navigation + +import android.app.Application +import android.content.Context +import android.content.Intent +import com.ichi2.anki.common.destinations.Destination +import com.ichi2.anki.common.destinations.Navigator + +/** AnkiDroid's [Navigator] implementation. */ +object AnkiDroidNavigator : Navigator { + private lateinit var appContext: Context + + fun initialize(application: Application) { + appContext = application + } + + override fun toIntent(destination: Destination): Intent = error("no Intent mapping registered for $destination") +} + +/** Initializes the AnkiDroid navigator and wires it up as the global [Navigator]. */ +context(application: Application) +fun initializeNavigator() { + AnkiDroidNavigator.initialize(application) + Navigator.register(AnkiDroidNavigator) +} diff --git a/anki-common/build.gradle.kts b/anki-common/build.gradle.kts index 09aac2d0f3b0..a6079fadc047 100644 --- a/anki-common/build.gradle.kts +++ b/anki-common/build.gradle.kts @@ -1,4 +1,3 @@ -// SPDX-FileCopyrightText: 2026 David Allison // SPDX-License-Identifier: GPL-3.0-or-later import com.android.build.api.dsl.LibraryExtension @@ -17,4 +16,7 @@ configure { dependencies { implementation(project(":common")) implementation(project(":libanki")) + + implementation(libs.androidx.activity) + implementation(libs.androidx.fragment.ktx) } diff --git a/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt new file mode 100644 index 000000000000..c35dc9114ff5 --- /dev/null +++ b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.common.destinations + +/** + * A target screen that can be navigated to. + * + * Concrete destinations are grouped under a [Destination] subclass per screen. + * + * To navigate to a destination, call [navigate]. + * + * @see Navigator - singleton registration and intent building + */ +sealed class Destination diff --git a/anki-common/src/main/java/com/ichi2/anki/common/destinations/Navigator.kt b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Navigator.kt new file mode 100644 index 000000000000..12e8aa9386c8 --- /dev/null +++ b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Navigator.kt @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.common.destinations + +import android.app.Activity +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import androidx.fragment.app.Fragment + +// TODO: Move this into anki-common:android after libanki becomes a java-library + +/** + * Global navigation instance, set during app initialization. + * + * Accessible via [navigate]. + */ +private lateinit var navigatorInstance: Navigator + +/** + * Resolves a [Destination] to an [Intent]. + * + * Implementations should use the application `Context` internally. + */ +interface Navigator { + // the intent constructor only uses context for the package name, + // so using the app context is acceptable. + fun toIntent(destination: Destination): Intent + + companion object { + /** + * Use during app startup to set the global [Navigator] instance. + * + * Placed on the companion object, so calers may use `Navigator.register` to avoid + * collisions with other top-level `register` functions. + */ + fun register(navigator: Navigator) { + navigatorInstance = navigator + } + } +} + +/** Starts the activity corresponding to [destination]. */ +context(activity: Activity) +fun navigate(destination: Destination) { + activity.startActivity(navigatorInstance.toIntent(destination)) +} + +/** Starts the activity corresponding to [destination] from the host of this Fragment. */ +context(fragment: Fragment) +fun navigate(destination: Destination) { + fragment.requireActivity().startActivity(navigatorInstance.toIntent(destination)) +} + +/** Launches [destination] via an [ActivityResultLauncher], so the caller can observe the result. */ +context(launcher: ActivityResultLauncher) +fun navigate(destination: Destination) { + launcher.launch(navigatorInstance.toIntent(destination)) +} From ca35222f5c13f36259fe9de71bb021a32ba6a815 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Mon, 11 May 2026 20:17:13 +0100 Subject: [PATCH 2/2] refactor(card-browser): move navigation to anki-common * AnkiDroidNavigator handles the top-level 'when(Destination)' * Each package contains `FeatureDestination.toIntent(Context)` * When a feature module split occurs, the nav implementation lives with the feature Issue 20558 - initial implementation of navigation pattern Assisted-by: Claude Opus 4.7 --- .../main/java/com/ichi2/anki/DeckPicker.kt | 2 + .../ichi2/anki/browser/BrowserDestination.kt | 45 +++---------------- .../anki/deckpicker/DeckPickerViewModel.kt | 6 ++- .../anki/navigation/AnkiDroidNavigator.kt | 7 ++- .../ui/windows/reviewer/ReviewerFragment.kt | 6 ++- .../ui/windows/reviewer/ReviewerViewModel.kt | 6 ++- .../java/com/ichi2/anki/utils/Destination.kt | 19 ++------ .../java/com/ichi2/anki/DeckPickerTest.kt | 12 +++-- .../common/destinations/BrowserDestination.kt | 24 ++++++++++ .../anki/common/destinations/Destination.kt | 3 ++ 10 files changed, 68 insertions(+), 62 deletions(-) create mode 100644 anki-common/src/main/java/com/ichi2/anki/common/destinations/BrowserDestination.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index c56dbc416755..50a50e870664 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -94,6 +94,7 @@ import com.ichi2.anki.android.input.ShortcutGroup import com.ichi2.anki.android.input.shortcut import com.ichi2.anki.common.annotations.NeedsTest import com.ichi2.anki.common.crashreporting.CrashReportService +import com.ichi2.anki.common.destinations.navigate import com.ichi2.anki.common.time.TimeManager import com.ichi2.anki.common.utils.annotation.KotlinCleanup import com.ichi2.anki.compat.CompatHelper.Companion.getSerializableCompat @@ -780,6 +781,7 @@ open class DeckPicker : viewModel.emptyCardsNotification.launchCollectionInLifecycleScope(::onCardsEmptied) viewModel.flowOfDeckCountsChanged.launchCollectionInLifecycleScope(::onDeckCountsChanged) viewModel.flowOfDestination.launchCollectionInLifecycleScope(::onDestinationChanged) + viewModel.flowOfNavigate.launchCollectionInLifecycleScope { navigate(it) } viewModel.flowOfExportDeck.launchCollectionInLifecycleScope(::onExportDeck) viewModel.flowOfCreateShortcut.launchCollectionInLifecycleScope(::createIcon) viewModel.flowOfDisableShortcuts.launchCollectionInLifecycleScope(::disableDeckAndChildrenShortcuts) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserDestination.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserDestination.kt index a5dc6b2e0108..c8780ea1a1ca 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserDestination.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserDestination.kt @@ -1,53 +1,22 @@ -/* - * Copyright (c) 2025 David Allison - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later package com.ichi2.anki.browser import android.content.Context import android.content.Intent import com.ichi2.anki.CardBrowser -import com.ichi2.anki.libanki.CardId -import com.ichi2.anki.libanki.DeckId -import com.ichi2.anki.utils.Destination +import com.ichi2.anki.common.destinations.BrowserDestination -/** - * Opens the [CardBrowser] - */ -sealed interface BrowserDestination : Destination { - data class ToDeck( - val deckId: DeckId, - ) : BrowserDestination { - override fun toIntent(context: Context): Intent = +/** Builds the [Intent] that launches [CardBrowser] for this destination. */ +fun BrowserDestination.toIntent(context: Context): Intent = + when (this) { + is BrowserDestination.ToDeck -> Intent(context, CardBrowser::class.java).apply { putExtra(CardBrowserViewModel.EXTRA_DECK_ID, deckId) } - } - - /** - * Opens the [CardBrowser] scoped to [deckId], auto-scrolling to [cardId] - * if the card is present on the deck. - */ - data class ScrollToCard( - val deckId: DeckId, - val cardId: CardId, - ) : BrowserDestination { - override fun toIntent(context: Context): Intent = + is BrowserDestination.ScrollToCard -> Intent(context, CardBrowser::class.java).apply { putExtra(CardBrowserViewModel.EXTRA_DECK_ID, deckId) putExtra(CardBrowserViewModel.EXTRA_CARD_ID_KEY, cardId) } } -} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt index 3fd85b345df7..74afcb6fa972 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt @@ -35,7 +35,7 @@ import com.ichi2.anki.DeckPicker import com.ichi2.anki.InitialActivity import com.ichi2.anki.OnErrorListener import com.ichi2.anki.PermissionSet -import com.ichi2.anki.browser.BrowserDestination +import com.ichi2.anki.common.destinations.BrowserDestination import com.ichi2.anki.configureRenderingMode import com.ichi2.anki.launchCatchingIO import com.ichi2.anki.libanki.CardId @@ -70,6 +70,7 @@ import kotlinx.coroutines.withContext import net.ankiweb.rsdroid.RustCleanup import net.ankiweb.rsdroid.exceptions.BackendNetworkException import timber.log.Timber +import com.ichi2.anki.common.destinations.Destination as NavigateDestination /** * ViewModel for the [DeckPicker] @@ -135,6 +136,7 @@ class DeckPickerViewModel : val deckDeletedNotification = MutableSharedFlow(extraBufferCapacity = 1) val emptyCardsNotification = MutableSharedFlow(extraBufferCapacity = 1) val flowOfDestination = MutableSharedFlow(extraBufferCapacity = 1) + val flowOfNavigate = MutableSharedFlow(extraBufferCapacity = 1) override val onError = MutableSharedFlow(extraBufferCapacity = 1) val flowOfExportDeck = MutableSharedFlow() val flowOfCreateShortcut = MutableSharedFlow() @@ -291,7 +293,7 @@ class DeckPickerViewModel : fun browseCards(deckId: DeckId) = launchCatchingIO { withCol { decks.select(deckId) } - flowOfDestination.emit(BrowserDestination.ToDeck(deckId)) + flowOfNavigate.emit(BrowserDestination.ToDeck(deckId)) } fun addNote( diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt b/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt index d34d6ce8c3eb..1fe787f1809b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/navigation/AnkiDroidNavigator.kt @@ -5,6 +5,8 @@ package com.ichi2.anki.navigation import android.app.Application import android.content.Context import android.content.Intent +import com.ichi2.anki.browser.toIntent +import com.ichi2.anki.common.destinations.BrowserDestination import com.ichi2.anki.common.destinations.Destination import com.ichi2.anki.common.destinations.Navigator @@ -16,7 +18,10 @@ object AnkiDroidNavigator : Navigator { appContext = application } - override fun toIntent(destination: Destination): Intent = error("no Intent mapping registered for $destination") + override fun toIntent(destination: Destination): Intent = + when (destination) { + is BrowserDestination -> destination.toIntent(appContext) + } } /** Initializes the AnkiDroid navigator and wires it up as the global [Navigator]. */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt index b20bea51de86..6e0b0f05235f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt @@ -53,9 +53,9 @@ import com.ichi2.anki.DispatchKeyEventListener import com.ichi2.anki.Flag import com.ichi2.anki.R import com.ichi2.anki.android.AnkiShakeDetector -import com.ichi2.anki.android.back.doubleBackPressCallback import com.ichi2.anki.cardviewer.Gesture import com.ichi2.anki.common.annotations.NeedsTest +import com.ichi2.anki.common.destinations.navigate import com.ichi2.anki.common.utils.android.isRobolectric import com.ichi2.anki.databinding.FragmentReviewerBinding import com.ichi2.anki.dialogs.showDeckOptionsSelectionDialog @@ -205,6 +205,10 @@ class ReviewerFragment : binding.rootLayout.requestFocus() } + viewModel.navigateFlow.collectIn(lifecycleScope) { destination -> + navigate(destination) + } + viewModel.destinationFlow.collectIn(lifecycleScope) { destination -> if (destination is DeckOptionsDestination && destination.options.size > 1) { requireContext().showDeckOptionsSelectionDialog(destination.options) { selectedOption -> diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt index ae83d0f9a764..84da9d6219fb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt @@ -27,9 +27,9 @@ import com.ichi2.anki.CollectionManager.withCol import com.ichi2.anki.Flag import com.ichi2.anki.Reviewer import com.ichi2.anki.asyncIO -import com.ichi2.anki.browser.BrowserDestination import com.ichi2.anki.cardviewer.SingleCardSide import com.ichi2.anki.common.annotations.NeedsTest +import com.ichi2.anki.common.destinations.BrowserDestination import com.ichi2.anki.launchCatchingIO import com.ichi2.anki.libanki.Card import com.ichi2.anki.libanki.CardId @@ -82,6 +82,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeoutOrNull import org.intellij.lang.annotations.Language import timber.log.Timber +import com.ichi2.anki.common.destinations.Destination as NavigateDestination class ReviewerViewModel( savedStateHandle: SavedStateHandle, @@ -113,6 +114,7 @@ class ReviewerViewModel( val onTypedAnswerResultFlow = MutableSharedFlow>() val onCardUpdatedFlow = MutableSharedFlow() val destinationFlow = MutableSharedFlow() + val navigateFlow = MutableSharedFlow() val editNoteTagsFlow = MutableSharedFlow() val setDueDateFlow = MutableSharedFlow() val resetProgressFlow = MutableSharedFlow() @@ -348,7 +350,7 @@ class ReviewerViewModel( val cardId = currentCard.await().id val destination = BrowserDestination.ScrollToCard(deckId, cardId) Timber.i("Launching 'browse options' for deck %d", deckId) - destinationFlow.emit(destination) + navigateFlow.emit(destination) } private suspend fun deleteNote() { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Destination.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Destination.kt index 20becd6b939f..aff3f20db656 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Destination.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Destination.kt @@ -1,23 +1,12 @@ -/* - * Copyright (c) 2025 Brayan Oliveira - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright (c) 2025 Brayan Oliveira + package com.ichi2.anki.utils import android.content.Context import android.content.Intent +// TODO: Replace with com.ichi2.anki.common.destinations.Destination + navigate(). See #20558. interface Destination { fun toIntent(context: Context): Intent } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt index 781fd1040835..cf125c1a0136 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt @@ -29,6 +29,7 @@ import com.ichi2.anki.dialogs.DeckPickerContextMenuResult import com.ichi2.anki.dialogs.setDeckPickerContextMenuResult import com.ichi2.anki.dialogs.utils.title import com.ichi2.anki.libanki.DeckId +import com.ichi2.anki.navigation.AnkiDroidNavigator import com.ichi2.anki.observability.ChangeManager import com.ichi2.anki.preferences.sharedPrefs import com.ichi2.anki.settings.Prefs @@ -48,6 +49,7 @@ import com.ichi2.testutils.grantWritePermissions import com.ichi2.testutils.revokeWritePermissions import com.ichi2.testutils.withDeniedPermissions import com.ichi2.testutils.withWritePermissions +import kotlinx.coroutines.flow.merge import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.containsString @@ -426,12 +428,16 @@ class DeckPickerTest : RobolectricTest() { option: ContextMenuOption, deckId: DeckId, ): Intent { - var result: Destination? = null - viewModel.flowOfDestination.test(1.seconds) { + var result: Any? = null + merge(viewModel.flowOfDestination, viewModel.flowOfNavigate).test(1.seconds) { selectContextMenuOption(option, deckId) result = awaitItem() } - return result!!.toIntent(this) + return when (val emitted = result!!) { + is Destination -> emitted.toIntent(this) + is com.ichi2.anki.common.destinations.Destination -> AnkiDroidNavigator.toIntent(emitted) + else -> error("Unexpected destination type: $emitted") + } } val didA = addDeck("Deck 1") diff --git a/anki-common/src/main/java/com/ichi2/anki/common/destinations/BrowserDestination.kt b/anki-common/src/main/java/com/ichi2/anki/common/destinations/BrowserDestination.kt new file mode 100644 index 000000000000..03784a07e367 --- /dev/null +++ b/anki-common/src/main/java/com/ichi2/anki/common/destinations/BrowserDestination.kt @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.common.destinations + +import com.ichi2.anki.libanki.CardId +import com.ichi2.anki.libanki.DeckId + +/** Opens the Card Browser. */ +// TODO: A number of destination are undefined - grep for CardBrowser::class (#20558) +sealed class BrowserDestination : Destination() { + /** Opens the Card Browser scoped to [deckId]. */ + data class ToDeck( + val deckId: DeckId, + ) : BrowserDestination() + + /** + * Opens the Card Browser scoped to [deckId], auto-scrolling to [cardId] + * if the card is present on the deck. + */ + data class ScrollToCard( + val deckId: DeckId, + val cardId: CardId, + ) : BrowserDestination() +} diff --git a/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt index c35dc9114ff5..c9b3f2244417 100644 --- a/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt +++ b/anki-common/src/main/java/com/ichi2/anki/common/destinations/Destination.kt @@ -9,6 +9,9 @@ package com.ichi2.anki.common.destinations * * To navigate to a destination, call [navigate]. * + * @see BrowserDestination - example destination subclass * @see Navigator - singleton registration and intent building */ sealed class Destination +// See `AnkiDroidNavigator` for all destinations +// it is not technically feasible for them to be listed here