├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ci-gradle.properties └── workflows │ ├── build.yaml │ ├── docs-publish.yaml │ ├── mkdocs-requirements.txt │ └── release.yaml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── Setup.kt ├── docs ├── android-viewmodel │ ├── hilt-integration.md │ ├── index.md │ └── viewmodel-kmp.md ├── back-press.md ├── community-projects.md ├── deep-links.md ├── faq.md ├── index.md ├── lifecycle.md ├── media │ ├── assets │ │ ├── Sem título-1.png │ │ ├── basic-nav.gif │ │ ├── ezgif.com-gif-maker (1).gif │ │ ├── ezgif.com-gif-maker.gif │ │ ├── fade.gif │ │ ├── navigation-android.gif │ │ ├── navigation-bottom-sheet.gif │ │ ├── nested-nav.gif │ │ ├── scale.gif │ │ ├── slide.gif │ │ ├── stack.gif │ │ └── tab-nav.gif │ └── icon │ │ └── logo.png ├── migration-to-1.0.0.md ├── navigation │ ├── bottomsheet-navigation.md │ ├── index.md │ ├── multi-module-navigation.md │ ├── nested-navigation.md │ └── tab-navigation.md ├── screenmodel │ ├── coroutines-integration.md │ ├── hilt-integration.md │ ├── index.md │ ├── kodein-integration.md │ ├── koin-integration.md │ ├── livedata-integration.md │ └── rxjava-integration.md ├── setup.md ├── stack-api.md ├── state-restoration.md └── transitions-api.md ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── includes.gradle.kts ├── kotlin-js-store └── yarn.lock ├── mkdocs.yml ├── samples ├── .gitignore ├── android │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── cafe │ │ │ └── adriel │ │ │ └── voyager │ │ │ └── sample │ │ │ ├── App.kt │ │ │ ├── SampleActivity.kt │ │ │ ├── SampleContent.kt │ │ │ ├── androidLegacy │ │ │ ├── LegacyActivity.kt │ │ │ ├── LegacyModule.kt │ │ │ ├── LegacyOneScreenModel.kt │ │ │ ├── LegacyScreenOne.kt │ │ │ ├── LegacyScreenTwo.kt │ │ │ └── LegacyTwoScreenModel.kt │ │ │ ├── androidViewModel │ │ │ ├── AndroidDetailsScreen.kt │ │ │ ├── AndroidDetailsViewModel.kt │ │ │ ├── AndroidListScreen.kt │ │ │ ├── AndroidListViewModel.kt │ │ │ └── AndroidViewModelActivity.kt │ │ │ ├── basicNavigation │ │ │ ├── BasicNavigationActivity.kt │ │ │ └── BasicNavigationScreen.kt │ │ │ ├── bottomSheetNavigation │ │ │ ├── BackScreen.kt │ │ │ └── BottomSheetNavigationActivity.kt │ │ │ ├── hiltIntegration │ │ │ ├── HiltDetailsScreen.kt │ │ │ ├── HiltDetailsScreenModel.kt │ │ │ ├── HiltDetailsViewModel.kt │ │ │ ├── HiltListScreen.kt │ │ │ ├── HiltListScreenModel.kt │ │ │ ├── HiltListViewModel.kt │ │ │ ├── HiltMainActivity.kt │ │ │ └── HiltModule.kt │ │ │ ├── kodeinIntegration │ │ │ ├── KodeinIntegrationActivity.kt │ │ │ ├── KodeinScopedDependencySample.kt │ │ │ ├── KodeinScreen.kt │ │ │ └── KodeinScreenModel.kt │ │ │ ├── koinIntegration │ │ │ ├── KoinIntegrationActivity.kt │ │ │ ├── KoinScreen.kt │ │ │ └── KoinScreenModel.kt │ │ │ ├── liveDataIntegration │ │ │ ├── LiveDataIntegrationActivity.kt │ │ │ ├── LiveDataScreen.kt │ │ │ └── LiveDataScreenModel.kt │ │ │ ├── nestedNavigation │ │ │ └── NestedNavigationActivity.kt │ │ │ ├── parcelableScreen │ │ │ ├── ParcelableActivity.kt │ │ │ └── SampleParcelableScreen.kt │ │ │ ├── rxjavaIntegration │ │ │ ├── RxJavaIntegrationActivity.kt │ │ │ ├── RxJavaScreen.kt │ │ │ └── RxJavaScreenModel.kt │ │ │ ├── screenModel │ │ │ ├── DetailsScreen.kt │ │ │ ├── DetailsScreenModel.kt │ │ │ ├── ListScreen.kt │ │ │ ├── ListScreenModel.kt │ │ │ └── ScreenModelActivity.kt │ │ │ ├── screenTransition │ │ │ ├── SampleScreens.kt │ │ │ ├── SampleTransitions.kt │ │ │ └── ScreenTransitionActivity.kt │ │ │ ├── stateStack │ │ │ └── StateStackActivity.kt │ │ │ └── tabNavigation │ │ │ ├── TabNavigationActivity.kt │ │ │ └── tabs │ │ │ ├── FavoritesTab.kt │ │ │ ├── HomeTab.kt │ │ │ ├── ProfileTab.kt │ │ │ └── TabContent.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_legacy.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ └── strings.xml ├── multi-module │ ├── .gitignore │ ├── app │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── cafe │ │ │ │ └── adriel │ │ │ │ └── voyager │ │ │ │ └── sample │ │ │ │ └── multimodule │ │ │ │ ├── SampleActivity.kt │ │ │ │ └── SampleApp.kt │ │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ └── values │ │ │ └── strings.xml │ ├── feature-home │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── cafe │ │ │ └── adriel │ │ │ └── voyager │ │ │ └── sample │ │ │ └── multimodule │ │ │ └── home │ │ │ └── HomeScreen.kt │ ├── feature-posts │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── cafe │ │ │ └── adriel │ │ │ └── voyager │ │ │ └── sample │ │ │ └── multimodule │ │ │ └── posts │ │ │ ├── DetailsScreen.kt │ │ │ ├── ListScreen.kt │ │ │ └── ScreenModule.kt │ └── navigation │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── sample │ │ └── multimodule │ │ └── navigation │ │ └── SharedScreen.kt ├── multiplatform-iosApp │ ├── Configuration │ │ └── Config.xcconfig │ ├── iosApp.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ │ └── gabriel.lopes.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── gabriel.lopes.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── iosApp │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ └── app-icon-1024.png │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Info.plist │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── iOSApp.swift └── multiplatform │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── cafe │ │ │ └── adriel │ │ │ └── voyager │ │ │ └── sample │ │ │ └── multiplatform │ │ │ └── SampleActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ └── strings.xml │ ├── commonMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── sample │ │ └── multiplatform │ │ ├── Application.kt │ │ └── BasicNavigationScreen.kt │ ├── desktopMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── sample │ │ └── multiplatform │ │ └── App.kt │ ├── iosMain │ └── kotlin │ │ └── MainViewController.kt │ ├── jsMain │ ├── kotlin │ │ └── main.js.kt │ └── resources │ │ ├── index.html │ │ └── style.css │ ├── macosMain │ └── kotlin │ │ └── main.macos.kt │ └── wasmJsMain │ ├── kotlin │ └── main.wasmJs.kt │ └── resources │ └── index.html ├── settings.gradle.kts ├── voyager-bottom-sheet-navigator ├── .gitignore ├── api │ ├── android │ │ └── voyager-bottom-sheet-navigator.api │ └── desktop │ │ └── voyager-bottom-sheet-navigator.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── bottomSheet │ │ └── internal │ │ └── Actuals.kt │ ├── commonMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── bottomSheet │ │ ├── BottomSheetNavigator.kt │ │ └── internal │ │ └── BottomSheetNavigatorBackHandler.kt │ ├── commonWebMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── bottomSheet │ │ └── internal │ │ └── Actuals.web.kt │ ├── desktopMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── bottomSheet │ │ └── internal │ │ └── Actuals.kt │ ├── iosMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── bottomSheet │ │ └── internal │ │ └── Actuals.uikit.kt │ └── macosMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── navigator │ └── bottomSheet │ └── internal │ └── Actuals.macos.kt ├── voyager-core ├── .gitignore ├── api │ ├── android │ │ └── voyager-core.api │ └── desktop │ │ └── voyager-core.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ ├── androidx │ │ └── AndroidScreenLifecycleOwner.kt │ │ └── core │ │ ├── concurrent │ │ └── PlatformDispatcher.android.kt │ │ └── lifecycle │ │ └── ConfigurationChecker.kt │ ├── commonMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── core │ │ ├── annotation │ │ └── InternalVoyagerApi.kt │ │ ├── concurrent │ │ ├── AtomicInt32.kt │ │ ├── PlatformDispatcher.kt │ │ ├── ThreadSafeList.kt │ │ ├── ThreadSafeMap.kt │ │ └── ThreadSafeSet.kt │ │ ├── lifecycle │ │ ├── ConfigurationChecker.kt │ │ ├── DisposableEffect.kt │ │ ├── LifecycleEffectStore.kt │ │ ├── NavigatorScreenLifecycle.kt │ │ ├── ScreenLifecycle.kt │ │ ├── ScreenLifecycleOwner.kt │ │ ├── ScreenLifecycleStore.kt │ │ ├── Serializable.kt │ │ └── multipleScreenLifecycleOwnerUtil.kt │ │ ├── platform │ │ └── KClassEx.kt │ │ ├── registry │ │ ├── ScreenModule.kt │ │ ├── ScreenProvider.kt │ │ └── ScreenRegistry.kt │ │ ├── screen │ │ ├── Screen.kt │ │ └── ScreenKey.kt │ │ └── stack │ │ ├── SnapshotStateStack.kt │ │ └── Stack.kt │ ├── commonWebMain │ └── kotlin │ │ └── cafe.adriel.voyager.core │ │ ├── concurrent │ │ ├── AtomicInt32.js.kt │ │ ├── PlatformDispatcher.js.kt │ │ ├── ThreadSafeList.js.kt │ │ ├── ThreadSafeMap.js.kt │ │ └── ThreadSafeSet.js.kt │ │ ├── lifecycle │ │ ├── ConfigurationChecker.kt │ │ └── Serializable.native.kt │ │ ├── platform │ │ └── KClassEx.js.kt │ │ └── screen │ │ ├── Screen.native.kt │ │ └── ScreenKey.web.kt │ ├── desktopMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── core │ │ ├── concurrent │ │ └── PlatformDispatcher.desktop.kt │ │ └── lifecycle │ │ └── ConfigurationChecker.kt │ ├── jvmMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── core │ │ ├── concurrent │ │ ├── AtomicInt32.kt │ │ ├── ThreadSafeList.kt │ │ ├── ThreadSafeMap.kt │ │ └── ThreadSafeSet.kt │ │ ├── lifecycle │ │ └── Serializable.jvm.kt │ │ ├── platform │ │ └── KClassEx.jvm.kt │ │ └── screen │ │ ├── Screen.kt │ │ └── ScreenKey.jvm.kt │ ├── jvmTest │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── core │ │ ├── stack │ │ └── SnapshotStateStackTest.kt │ │ └── utils │ │ └── Quadruple.kt │ └── nativeMain │ └── kotlin │ └── cafe.adriel.voyager.core │ ├── concurrent │ ├── AtomicInt32.native.kt │ ├── PlatformDispatcher.native.kt │ ├── ThreadSafeList.native.kt │ ├── ThreadSafeMap.native.kt │ ├── ThreadSafeMutableCollection.kt │ ├── ThreadSafeMutableIterator.kt │ └── ThreadSafeSet.native.kt │ ├── lifecycle │ ├── ConfigurationChecker.kt │ └── Serializable.native.kt │ ├── platform │ └── KClassEx.native.kt │ └── screen │ ├── Screen.native.kt │ └── ScreenKey.native.kt ├── voyager-hilt ├── .gitignore ├── api │ └── voyager-hilt.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── cafe │ └── adriel │ └── voyager │ └── hilt │ ├── OptionalMultibindingsModule.kt │ ├── ScreenModel.kt │ ├── ScreenModelEntryPoint.kt │ ├── ScreenModelFactory.kt │ ├── ScreenModelFactoryKey.kt │ ├── ScreenModelKey.kt │ ├── ViewModel.kt │ ├── VoyagerHiltViewModelFactories.kt │ └── internal │ └── ContextExt.kt ├── voyager-kodein ├── .gitignore ├── api │ ├── android │ │ └── voyager-kodein.api │ └── desktop │ │ └── voyager-kodein.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── kodein │ ├── ScreenLifecycleScope.kt │ └── ScreenModel.kt ├── voyager-koin ├── .gitignore ├── api │ ├── android │ │ └── voyager-koin.api │ └── desktop │ │ └── voyager-koin.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── koin │ └── ScreenModel.kt ├── voyager-lifecycle-kmp ├── .gitignore ├── api │ ├── android │ │ └── voyager-lifecycle-kmp.api │ └── desktop │ │ └── voyager-lifecycle-kmp.api ├── build.gradle.kts ├── gradle.properties └── src │ ├── androidMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── jetpack │ │ └── AndroidScreenLifecycleOwner.android.kt │ ├── commonMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── jetpack │ │ ├── LifecycleProvider.kt │ │ ├── NavigatorLifecycleKMPOwner.kt │ │ ├── ScreenLifecycleJetpackOwner.kt │ │ ├── VoyagerLifecycleKMPOwner.kt │ │ └── navigator.kt │ └── nonAndroidMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── jetpack │ └── AndroidScreenLifecycleOwner.nonAndroid.kt ├── voyager-livedata ├── .gitignore ├── api │ └── voyager-livedata.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── cafe │ └── adriel │ └── voyager │ └── livedata │ └── LiveScreenModel.kt ├── voyager-navigator ├── .gitignore ├── api │ ├── android │ │ └── voyager-navigator.api │ └── desktop │ │ └── voyager-navigator.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ ├── NavigatorSaver.android.kt │ │ └── internal │ │ ├── Actuals.kt │ │ └── LifecycleProvider.android.kt │ ├── commonMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ ├── Navigator.kt │ │ ├── NavigatorSaver.kt │ │ ├── internal │ │ ├── LifecycleProvider.kt │ │ ├── NavigatorBackHandler.kt │ │ ├── NavigatorDisposable.kt │ │ └── NavigatorSaverInternal.kt │ │ └── lifecycle │ │ ├── NavigatorDisposable.kt │ │ └── NavigatorLifecycleStore.kt │ ├── commonWebMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── internal │ │ └── Actuals.web.kt │ ├── desktopMain │ └── kotlin │ │ └── cafe │ │ └── adriel │ │ └── voyager │ │ └── navigator │ │ └── internal │ │ └── Actuals.kt │ ├── iosMain │ └── kotlin │ │ └── cafe.adriel.voyager.navigator.internal │ │ └── Actuals.uikit.kt │ ├── macosMain │ └── kotlin │ │ └── cafe.adriel.voyager.navigator.internal │ │ └── Actuals.macos.kt │ └── nonAndroidMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── navigator │ └── internal │ └── LifecycleProvider.nonAndroid.kt ├── voyager-rxjava ├── .gitignore ├── api │ ├── android │ │ └── voyager-rxjava.api │ └── desktop │ │ └── voyager-rxjava.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── jvmMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── rxjava │ └── ScreenModel.kt ├── voyager-screenmodel ├── .gitignore ├── api │ ├── android │ │ └── voyager-screenmodel.api │ └── desktop │ │ └── voyager-screenmodel.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── core │ └── model │ ├── NavigatorScreenModel.kt │ ├── ScreenModel.kt │ └── ScreenModelStore.kt ├── voyager-tab-navigator ├── .gitignore ├── api │ ├── android │ │ └── voyager-tab-navigator.api │ └── desktop │ │ └── voyager-tab-navigator.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── cafe │ └── adriel │ └── voyager │ └── navigator │ └── tab │ ├── Tab.kt │ └── TabNavigator.kt └── voyager-transitions ├── .gitignore ├── api ├── android │ └── voyager-transitions.api └── desktop │ └── voyager-transitions.api ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties └── src ├── androidMain └── AndroidManifest.xml └── commonMain └── kotlin └── cafe └── adriel └── voyager └── transitions ├── CrossfadeTransition.kt ├── FadeTransition.kt ├── ScaleTransition.kt ├── ScreenTransition.kt └── SlideTransition.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{java,kt,kts,xml,kt.spec,kts.spec}] 4 | charset = utf-8 5 | insert_final_newline = true 6 | max_line_length = 120 7 | end_of_line = crlf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | * text eol=lf 3 | 4 | # Windows forced line-endings 5 | /.idea/* text eol=crlf 6 | *.bat text eol=crlf 7 | *.ps1 text eol=crlf 8 | 9 | # Gradle wrapper 10 | *.jar binary 11 | 12 | # Images 13 | *.webp binary 14 | *.png binary 15 | *.jpg binary 16 | *.jpeg binary 17 | *.gif binary 18 | *.ico binary 19 | *.icns binary 20 | *.gz binary 21 | *.zip binary 22 | *.7z binary 23 | *.ttf binary 24 | *.eot binary 25 | *.woff binary 26 | *.pyc binary 27 | *.swp binary 28 | *.pdf binary -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: adrielcafe -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | 3 | org.gradle.parallel=true 4 | org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError 5 | #org.gradle.workers.max=2 6 | 7 | kotlin.compiler.execution.strategy=in-process -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: macOS-latest 13 | timeout-minutes: 60 14 | steps: 15 | 16 | - name: Fetch Sources 17 | uses: actions/checkout@v3 18 | 19 | - name: Copy CI gradle.properties 20 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 21 | 22 | - name: Setup Java 23 | uses: actions/setup-java@v3 24 | with: 25 | java-version: 17 26 | distribution: 'temurin' 27 | 28 | - name: Lint 29 | run: | 30 | ./gradlew ktlintCheck --stacktrace 31 | 32 | - name: Api Check 33 | run: | 34 | ./gradlew apiCheck --stacktrace 35 | 36 | - name: Validate publish 37 | run: | 38 | ./gradlew -Pversion=1.0.0-SNAPSHOT publishToMavenLocal --stacktrace -------------------------------------------------------------------------------- /.github/workflows/docs-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Docs Publish 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | 8 | build: 9 | name: Docs Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Fetch Sources 14 | uses: actions/checkout@v3 15 | 16 | # Documentation publishing 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: 3.8 22 | 23 | - name: Build mkdocs 24 | run: | 25 | pip3 install -r .github/workflows/mkdocs-requirements.txt 26 | mkdocs build 27 | 28 | - name: Deploy 🚀 29 | if: success() 30 | uses: JamesIves/github-pages-deploy-action@releases/v3 31 | with: 32 | GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} 33 | BRANCH: gh-pages # The branch the action should deploy to. 34 | FOLDER: site # The folder the action should deploy. 35 | SINGLE_COMMIT: true -------------------------------------------------------------------------------- /.github/workflows/mkdocs-requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.7 2 | future==1.0.0 3 | Jinja2==3.1.3 4 | livereload==2.6.3 5 | lunr==0.7.0.post1 6 | MarkupSafe==2.1.5 7 | mkdocs==1.5.3 8 | mkdocs-macros-plugin==1.0.5 9 | mkdocs-material==9.5.18 10 | mkdocs-material-extensions==1.3.1 11 | mkdocs-minify-plugin==0.7.1 12 | Pygments==2.17.2 13 | pymdown-extensions==10.8.1 14 | python-dateutil==2.9.0.post0 15 | PyYAML==6.0.1 16 | repackage==0.7.3 17 | six==1.16.0 18 | termcolor==2.4.0 19 | tornado==6.4 -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [prereleased, released] 5 | 6 | jobs: 7 | 8 | release: 9 | name: Publish Voyager 10 | runs-on: macOS-latest 11 | timeout-minutes: 60 12 | steps: 13 | 14 | - name: Fetch Sources 15 | uses: actions/checkout@v3 16 | with: 17 | ref: ${{ github.event.release.tag_name }} 18 | 19 | - name: Copy CI gradle.properties 20 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 21 | 22 | - name: Setup Java 23 | uses: actions/setup-java@v3 24 | with: 25 | java-version: 17 26 | distribution: 'temurin' 27 | 28 | - name: Api Check 29 | run: | 30 | ./gradlew apiCheck --stacktrace 31 | 32 | - name: Deploy to Sonatype 33 | run: | 34 | NEW_VERSION=$(echo "${GITHUB_REF}" | cut -d "/" -f3) 35 | echo "New version: ${NEW_VERSION}" 36 | ./gradlew -Pversion=${NEW_VERSION} publish --stacktrace 37 | env: 38 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 39 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 40 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }} 41 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /local.properties 3 | /build 4 | /captures 5 | .DS_Store 6 | .externalNativeBuild 7 | .gradle 8 | *.iml 9 | *.focus -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2021 Adriel Café 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | google() 5 | gradlePluginPortal() 6 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev" ) 7 | } 8 | 9 | dependencies { 10 | classpath(libs.plugin.hilt) 11 | classpath(libs.plugin.ktlint) 12 | classpath(libs.plugin.maven) 13 | classpath(libs.plugin.multiplatform.compose) 14 | classpath(libs.plugin.atomicfu) 15 | } 16 | } 17 | 18 | plugins { 19 | alias(libs.plugins.binaryCompatibilityValidator) 20 | } 21 | 22 | subprojects { 23 | apply(plugin = "org.jlleitschuh.gradle.ktlint") 24 | 25 | configure { 26 | version.set("0.47.1") 27 | disabledRules.set(setOf("filename")) 28 | } 29 | } 30 | 31 | apiValidation { 32 | ignoredProjects.addAll(listOf( 33 | /*samples*/"android", 34 | /*samples*/"multiplatform", 35 | /*samples/multi-modulo*/"app", 36 | /*samples/multi-modulo*/"feature-home", 37 | /*samples/multi-modulo*/"feature-posts", 38 | /*samples/multi-modulo*/"navigation", 39 | )) 40 | nonPublicMarkers.addAll(listOf( 41 | "cafe.adriel.voyager.core.annotation.InternalVoyagerApi", 42 | "cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi" 43 | )) 44 | } 45 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.gradle -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation(libs.plugin.android) 13 | implementation(libs.plugin.kotlin) 14 | implementation("com.squareup:javapoet:1.13.0") // https://github.com/google/dagger/issues/3068 15 | } 16 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../gradle/libs.versions.toml")) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/android-viewmodel/hilt-integration.md: -------------------------------------------------------------------------------- 1 | # Hilt integration 2 | 3 | !!! success 4 | To use the `getViewModel` you should first import `cafe.adriel.voyager:voyager-hilt` (see [Setup](../setup.md)). 5 | 6 | ### @Inject 7 | 8 | Add `@HiltViewModel` and `@Inject` annotations to your `ViewModel`. 9 | 10 | ```kotlin 11 | @HiltViewModel 12 | class HomeViewModel @Inject constructor() : ViewModel() { 13 | // ... 14 | } 15 | ``` 16 | 17 | Call `getViewModel()` to get a new instance. 18 | 19 | ```kotlin 20 | class HomeScreen : Screen { 21 | 22 | @Composable 23 | override fun Content() { 24 | val screenModel = getViewModel() 25 | // ... 26 | } 27 | } 28 | ``` 29 | 30 | ### @AssistedInject 31 | 32 | Currently there's no Assisted Injection support for Hilt ViewModels ([issue](https://github.com/google/dagger/issues/2287)). 33 | 34 | ### Sample 35 | 36 | !!! info 37 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration). 38 | -------------------------------------------------------------------------------- /docs/android-viewmodel/index.md: -------------------------------------------------------------------------------- 1 | # Android ViewModel 2 | 3 | ```kotlin 4 | class PostListScreen : Screen { 5 | 6 | @Composable 7 | override fun Content() { 8 | val viewModel = viewModel() 9 | // ... 10 | } 11 | } 12 | ``` 13 | 14 | By default Voyager provides its own `LocalViewModelStoreOwner` and `LocalSavedStateRegistryOwner`, that way you can safely create `ViewModel`s without depending on `Activity` or `Fragment`. 15 | 16 | !!! info 17 | Voyager provides a similar implementation, the [ScreenModel](../screenmodel/README.md), which does the same as `ViewModel` but also works with [Compose Multiplatform](https://github.com/jetbrains/compose-jb). 18 | 19 | ### Sample 20 | 21 | ![](../media/assets/navigation-android.gif) 22 | 23 | !!! info 24 | Source code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel). 25 | -------------------------------------------------------------------------------- /docs/android-viewmodel/viewmodel-kmp.md: -------------------------------------------------------------------------------- 1 | # ViewModel KMP 2 | 3 | Since 1.1.0-beta01 we have introduce a experimental API for ViewModel KMP. It is under the package `cafe.adriel.voyager:voyager-lifecycle-kmp` (see [Setup](../setup.md)). 4 | 5 | You will need to call `ProvideNavigatorLifecycleKMPSupport` before all `Navigator` calls and it will be working out of the box. 6 | 7 | ```kotlin 8 | @Composable 9 | fun MainView() { 10 | ProvideNavigatorLifecycleKMPSupport { 11 | Navigator(...) 12 | } 13 | } 14 | 15 | class MyScreen : Screen { 16 | @Composable 17 | fun Content() { 18 | val myViewModel = viewModel { MyScreenViewModel() } 19 | } 20 | } 21 | ``` 22 | 23 | ## Navigator scoped ViewModel 24 | 25 | Voyager 1.1.0-beta01 also have introduced the support for Navigator scoped ViewModel and Lifecycle. 26 | This will make easy to share a ViewModel cross screen of the same navigator. 27 | 28 | ```kotlin 29 | class MyScreen : Screen { 30 | @Composable 31 | fun Content() { 32 | val myViewModel = navigatorViewModel { MyScreenViewModel() } 33 | } 34 | } 35 | ``` 36 | 37 | ## Lifecycle KMP 38 | 39 | This version also brings the Lifecycle events for Screen lifecycle in KMP, now is possible to 40 | a generic third party API that listen to Lifecycle of a Screen in KMP. 41 | -------------------------------------------------------------------------------- /docs/back-press.md: -------------------------------------------------------------------------------- 1 | # Back press 2 | 3 | By default, Voyager will handle back presses but you can override its behavior. Use the `onBackPressed` to manually handle it: return `true` to pop the current screen, or false otherwise. To disable, just set to `null`. 4 | 5 | ```kotlin 6 | setContent { 7 | Navigator( 8 | initialScreen = HomeScreen, 9 | onBackPressed = { currentScreen -> 10 | false // won't pop the current screen 11 | // true will pop, default behavior 12 | } 13 | // To disable: 14 | // onBackPressed = null 15 | ) 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/deep-links.md: -------------------------------------------------------------------------------- 1 | # Deep links 2 | 3 | !!! warning 4 | Currently Voyager does not provided a built in solution to handle Deeplink and URIs. see [#149](https://github.com/adrielcafe/voyager/issues/149) and [#382](https://github.com/adrielcafe/voyager/issues/382) 5 | 6 | You can initialize the `Navigator` with multiple screens, that way, the first visible screen will be the last one and will be possible to return (`pop()`) to the previous screens. 7 | 8 | ```kotlin 9 | val postId = getPostIdFromIntent() 10 | 11 | setContent { 12 | Navigator( 13 | HomeScreen, 14 | PostListScreen(), 15 | PostDetailsScreen(postId) 16 | ) 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Faq 2 | 3 | [iOS Swipe Back support](#ios-swipeback) { #ios-swipeback } 4 | 5 | Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all 6 | solutions that have out there and we think we will better of using a community made solution by copying 7 | to your code base and be able to change as you want your app to behave. 8 | 9 | See this [issue](https://github.com/adrielcafe/voyager/issues/144) each for community build solutions. 10 | 11 | Alternatively, we can also discuss in the future a community build solution using `NavigationController` 12 | under the hood like [`compose-cupertino`](https://github.com/alexzhirkevich/compose-cupertino/blob/master/cupertino-decompose/src/iosMain/kotlin/io/github/alexzhirkevich/cupertino/decompose/UIKitChildren.kt#L192) have implemented for Decompose. 13 | 14 | [Support for predictive back animations](#predictive-back) { #predictive-back } 15 | 16 | Voyager does not have a built in support for predictive back yet, but as well as iOS Swipe Back, the 17 | community have build extensions, and snippets with support, see 18 | [#223](https://github.com/adrielcafe/voyager/issues/223) and [144](https://github.com/adrielcafe/voyager/issues/144). 19 | 20 | [Support for result passing between screens](#result-passing) { #result-passing } 21 | 22 | Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all 23 | solutions that have out there, we encourage to use community made solutions by copying to your 24 | code base and being free to extend as your code base needs. 25 | 26 | See [#128](https://github.com/adrielcafe/voyager/pull/128) comments for community solutions. 27 | 28 | [Deeplink support](./deep-links.md) 29 | -------------------------------------------------------------------------------- /docs/media/assets/Sem título-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/Sem título-1.png -------------------------------------------------------------------------------- /docs/media/assets/basic-nav.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/basic-nav.gif -------------------------------------------------------------------------------- /docs/media/assets/ezgif.com-gif-maker (1).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/ezgif.com-gif-maker (1).gif -------------------------------------------------------------------------------- /docs/media/assets/ezgif.com-gif-maker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/ezgif.com-gif-maker.gif -------------------------------------------------------------------------------- /docs/media/assets/fade.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/fade.gif -------------------------------------------------------------------------------- /docs/media/assets/navigation-android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/navigation-android.gif -------------------------------------------------------------------------------- /docs/media/assets/navigation-bottom-sheet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/navigation-bottom-sheet.gif -------------------------------------------------------------------------------- /docs/media/assets/nested-nav.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/nested-nav.gif -------------------------------------------------------------------------------- /docs/media/assets/scale.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/scale.gif -------------------------------------------------------------------------------- /docs/media/assets/slide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/slide.gif -------------------------------------------------------------------------------- /docs/media/assets/stack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/stack.gif -------------------------------------------------------------------------------- /docs/media/assets/tab-nav.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/assets/tab-nav.gif -------------------------------------------------------------------------------- /docs/media/icon/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/docs/media/icon/logo.png -------------------------------------------------------------------------------- /docs/navigation/nested-navigation.md: -------------------------------------------------------------------------------- 1 | # Nested navigation 2 | 3 | ### Nested Navigators 4 | 5 | Going a little further, it's possible to have nested navigators. The `Navigator` has a `level` property (so you can check how deeper your are) and can have a `parent` navigator (if you need to interact with it). 6 | 7 | ```kotlin 8 | setContent { 9 | Navigator(ScreenA) { navigator0 -> 10 | println(navigator.level) 11 | // 0 12 | println(navigator.parent == null) 13 | // true 14 | Navigator(ScreenB) { navigator1 -> 15 | println(navigator.level) 16 | // 1 17 | println(navigator.parent == navigator0) 18 | // true 19 | Navigator(ScreenC) { navigator2 -> 20 | println(navigator.level) 21 | // 2 22 | println(navigator.parent == navigator1) 23 | // true 24 | } 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | Another operation is the `popUntilRoot()`, it will recursively pop all screens starting from the leaf navigator until the root one. 31 | 32 | ### Sample 33 | 34 | ![](../media/assets/nested-nav.gif) 35 | 36 | !!! info 37 | Source code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/nestedNavigation). -------------------------------------------------------------------------------- /docs/screenmodel/kodein-integration.md: -------------------------------------------------------------------------------- 1 | # Kodein integration 2 | 3 | !!! success 4 | To use the `rememberScreenModel` you should first import `cafe.adriel.voyager:voyager-kodein` (see [Setup](../setup.md)). 5 | 6 | Declare your `ScreenModel`s using the `bindProvider` bind. 7 | 8 | ```kotlin 9 | val homeModule = DI.Module(name = "home") { 10 | bindProvider { HomeScreenModel() } 11 | } 12 | ``` 13 | 14 | Call `rememberScreenModel()` to get a new instance. 15 | 16 | ```kotlin 17 | class HomeScreen : Screen { 18 | 19 | @Composable 20 | override fun Content() { 21 | val screenModel = rememberScreenModel() 22 | // ... 23 | } 24 | } 25 | ``` 26 | 27 | ### Sample 28 | 29 | !!! info 30 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration). 31 | 32 | ### Navigator scoped ScreenModel 33 | 34 | ```kotlin 35 | class HomeScreen : Screen { 36 | 37 | @Composable 38 | override fun Content() { 39 | val navigator = LocalNavigator.currentOrThrow 40 | val screenModel = navigator.rememberNavigatorScreenModel() 41 | // ... 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/screenmodel/koin-integration.md: -------------------------------------------------------------------------------- 1 | # Koin integration 2 | 3 | !!! success 4 | To use the `getScreenModel` you should first import `cafe.adriel.voyager:voyager-koin` (see [Setup](../setup.md)). 5 | 6 | !!! warning 7 | Since [1.1.0-alpha04](https://github.com/adrielcafe/voyager/releases/tag/1.1.0-alpha04) we have rename the `getScreenModel` to `koinScreenModel`, this is a change to follow Koin Compose naming schema. The previous `getScreenModel` is deprecated and will be removed on 1.1.0 8 | 9 | Declare your `ScreenModel`s using the `factory` component. 10 | 11 | ```kotlin 12 | val homeModule = module { 13 | factory { HomeScreenModel() } 14 | } 15 | ``` 16 | 17 | Call `getScreenModel()` to get a new instance. 18 | 19 | ```kotlin 20 | class HomeScreen : Screen { 21 | 22 | @Composable 23 | override fun Content() { 24 | val screenModel = getScreenModel() 25 | // ... 26 | } 27 | } 28 | ``` 29 | 30 | ### Sample 31 | 32 | !!! info 33 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration). 34 | 35 | ### Navigator scoped ScreenModel 36 | 37 | ```kotlin 38 | class HomeScreen : Screen { 39 | 40 | @Composable 41 | override fun Content() { 42 | val navigator = LocalNavigator.currentOrThrow 43 | val screenModel = navigator.getNavigatorScreenModel() 44 | // ... 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/screenmodel/livedata-integration.md: -------------------------------------------------------------------------------- 1 | # LiveData integration 2 | 3 | !!! success 4 | To use the `LiveScreenModel` you should first import `cafe.adriel.voyager:voyager-livedata` (see [Setup](../setup.md)). 5 | 6 | ### State-aware ScreenModel 7 | 8 | If your `ScreenModel` needs to provide a state, use the `LiveScreenModel`. Set the initial state on the constructor and use `mutableState` to change the current state. 9 | 10 | ```kotlin 11 | class PostDetailsScreenModel( 12 | private val repository: PostRepository 13 | ) : LiveScreenModel(State.Init) { 14 | 15 | sealed class State { 16 | object Init : State() 17 | object Loading : State() 18 | data class Result(val post: Post) : State() 19 | } 20 | 21 | fun getPost(id: String) { 22 | coroutineScope.launch { 23 | val result = State.Result(post = repository.getPost(id)) 24 | mutableState.postValue(result) 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | In your screen use `state.observeAsState()` and handle the current state. 31 | 32 | ```kotlin 33 | class PostDetailsScreen : Screen { 34 | 35 | @Composable 36 | override fun Content() { 37 | val screenModel = rememberScreenModel() 38 | val state by screenModel.state.observeAsState() 39 | 40 | when (state) { 41 | is State.Loading -> LoadingContent() 42 | is State.Result -> PostContent(state.post) 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### Sample 49 | 50 | !!! info 51 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration). 52 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /includes.gradle.kts: -------------------------------------------------------------------------------- 1 | include( 2 | ":samples:android", 3 | ":samples:multiplatform", 4 | 5 | ":samples:multi-module:app", 6 | ":samples:multi-module:navigation", 7 | ":samples:multi-module:feature-home", 8 | ":samples:multi-module:feature-posts", 9 | 10 | ":voyager-core", 11 | ":voyager-screenmodel", 12 | ":voyager-navigator", 13 | ":voyager-lifecycle-kmp", 14 | ":voyager-tab-navigator", 15 | ":voyager-bottom-sheet-navigator", 16 | ":voyager-transitions", 17 | ":voyager-hilt", 18 | ":voyager-kodein", 19 | ":voyager-koin", 20 | ":voyager-rxjava", 21 | ":voyager-livedata" 22 | ) 23 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /debug 3 | /release -------------------------------------------------------------------------------- /samples/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | kotlin("kapt") 5 | id("kotlin-parcelize") 6 | id("dagger.hilt.android.plugin") 7 | } 8 | 9 | setupModuleForAndroidxCompose( 10 | withKotlinExplicitMode = false 11 | ) 12 | 13 | android { 14 | namespace = "cafe.adriel.voyager.sample" 15 | defaultConfig { 16 | applicationId = "cafe.adriel.voyager.sample" 17 | } 18 | } 19 | 20 | kapt { 21 | correctErrorTypes = true 22 | } 23 | 24 | dependencies { 25 | implementation(projects.voyagerScreenmodel) 26 | implementation(projects.voyagerNavigator) 27 | implementation(projects.voyagerTabNavigator) 28 | implementation(projects.voyagerBottomSheetNavigator) 29 | implementation(projects.voyagerTransitions) 30 | implementation(projects.voyagerHilt) 31 | implementation(projects.voyagerKodein) 32 | implementation(projects.voyagerKoin) 33 | implementation(projects.voyagerRxjava) 34 | implementation(projects.voyagerLivedata) 35 | 36 | implementation(libs.kodein) 37 | implementation(libs.koin) 38 | implementation(libs.appCompat) 39 | implementation(libs.lifecycle.viewModelKtx) 40 | implementation(libs.lifecycle.viewModelCompose) 41 | implementation(libs.compose.rxjava) 42 | implementation(libs.compose.compiler) 43 | implementation(libs.compose.runtime) 44 | implementation(libs.compose.runtimeLiveData) 45 | implementation(libs.compose.activity) 46 | implementation(libs.compose.material) 47 | implementation(libs.compose.materialIcons) 48 | implementation(libs.compose.animation) 49 | implementation(libs.hilt.android) 50 | kapt(libs.hilt.compiler) 51 | 52 | debugImplementation(libs.leakCanary) 53 | } 54 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidLegacy 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.compose.ui.platform.ComposeView 6 | import cafe.adriel.voyager.navigator.Navigator 7 | import cafe.adriel.voyager.sample.R 8 | import dagger.hilt.android.AndroidEntryPoint 9 | 10 | @AndroidEntryPoint 11 | class LegacyActivity : AppCompatActivity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_legacy) 15 | val composeView = findViewById(R.id.composeView) 16 | composeView.setContent { 17 | Navigator(LegacyScreenOne()) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyModule.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidLegacy 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.hilt.ScreenModelKey 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ActivityComponent 9 | import dagger.multibindings.IntoMap 10 | 11 | @Module 12 | @InstallIn(ActivityComponent::class) 13 | abstract class LegacyModule { 14 | @Binds 15 | @IntoMap 16 | @ScreenModelKey(LegacyOneScreenModel::class) 17 | abstract fun bindLegacyOneScreenModel(legacyOneScreenModel: LegacyOneScreenModel): ScreenModel 18 | 19 | @Binds 20 | @IntoMap 21 | @ScreenModelKey(LegacyTwoScreenModel::class) 22 | abstract fun bindLegacyTwoScreenModel(legacyTwoScreenModel: LegacyTwoScreenModel): ScreenModel 23 | } 24 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyOneScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidLegacy 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import javax.inject.Inject 5 | 6 | class LegacyOneScreenModel @Inject constructor() : ScreenModel { 7 | val text = "I'm legacy one screen model" 8 | 9 | override fun onDispose() { 10 | println(">>>> disposing $this") 11 | super.onDispose() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyTwoScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidLegacy 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import javax.inject.Inject 5 | 6 | class LegacyTwoScreenModel @Inject constructor() : ScreenModel { 7 | val text = "I'm legacy two screen model" 8 | 9 | override fun onDispose() { 10 | println(">>>> disposing $this") 11 | super.onDispose() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidDetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidViewModel 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.screen.Screen 5 | import cafe.adriel.voyager.core.screen.ScreenKey 6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 7 | import cafe.adriel.voyager.navigator.LocalNavigator 8 | import cafe.adriel.voyager.navigator.currentOrThrow 9 | import cafe.adriel.voyager.sample.DetailsContent 10 | import org.koin.androidx.compose.koinViewModel 11 | import org.koin.core.parameter.parametersOf 12 | 13 | data class AndroidDetailsScreen( 14 | val index: Int 15 | ) : Screen { 16 | override val key: ScreenKey = uniqueScreenKey 17 | 18 | @Composable 19 | override fun Content() { 20 | val navigator = LocalNavigator.currentOrThrow 21 | val viewModel = koinViewModel { parametersOf(index) } 22 | 23 | DetailsContent(viewModel, "Item #${viewModel.index}", navigator::pop) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidDetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidViewModel 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class AndroidDetailsViewModel( 6 | val index: Int 7 | ) : ViewModel() { 8 | 9 | override fun onCleared() { 10 | println("ViewModel: clear details") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidListScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidViewModel 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.lifecycle.viewmodel.compose.viewModel 5 | import cafe.adriel.voyager.core.screen.Screen 6 | import cafe.adriel.voyager.core.screen.ScreenKey 7 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 8 | import cafe.adriel.voyager.navigator.LocalNavigator 9 | import cafe.adriel.voyager.navigator.currentOrThrow 10 | import cafe.adriel.voyager.sample.ListContent 11 | 12 | class AndroidListScreen : Screen { 13 | override val key: ScreenKey = uniqueScreenKey 14 | 15 | @Composable 16 | override fun Content() { 17 | val navigator = LocalNavigator.currentOrThrow 18 | val viewModel = viewModel() 19 | 20 | ListContent(viewModel.items, onClick = { index -> navigator.push(AndroidDetailsScreen(index)) }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidListViewModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidViewModel 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import cafe.adriel.voyager.sample.sampleItems 6 | 7 | class AndroidListViewModel(private val handle: SavedStateHandle) : ViewModel() { 8 | 9 | init { 10 | if (handle.get>("items").isNullOrEmpty()) { 11 | handle["items"] = sampleItems 12 | } 13 | } 14 | 15 | val items: List 16 | get() = handle["items"] ?: error("Items not found") 17 | 18 | override fun onCleared() { 19 | println("ViewModel: clear list") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidViewModelActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.androidViewModel 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class AndroidViewModelActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(AndroidListScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/basicNavigation/BasicNavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.basicNavigation 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import cafe.adriel.voyager.navigator.Navigator 8 | 9 | class BasicNavigationActivity : ComponentActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | 14 | setContent { 15 | Navigator( 16 | screen = BasicNavigationScreen(index = 0), 17 | onBackPressed = { currentScreen -> 18 | Log.d("Navigator", "Pop screen #${(currentScreen as BasicNavigationScreen).index}") 19 | true 20 | } 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/bottomSheetNavigation/BackScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.bottomSheetNavigation 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material.Button 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import cafe.adriel.voyager.core.screen.Screen 11 | import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator 12 | import cafe.adriel.voyager.sample.basicNavigation.BasicNavigationScreen 13 | 14 | class BackScreen : Screen { 15 | 16 | @Composable 17 | override fun Content() { 18 | val bottomSheetNavigator = LocalBottomSheetNavigator.current 19 | 20 | Box( 21 | contentAlignment = Alignment.Center, 22 | modifier = Modifier.fillMaxSize() 23 | ) { 24 | Button( 25 | onClick = { bottomSheetNavigator.show(BasicNavigationScreen(index = 0, wrapContent = true)) } 26 | ) { 27 | Text(text = "Show BottomSheet") 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/bottomSheetNavigation/BottomSheetNavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.bottomSheetNavigation 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material.ExperimentalMaterialApi 7 | import cafe.adriel.voyager.navigator.Navigator 8 | import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator 9 | 10 | class BottomSheetNavigationActivity : ComponentActivity() { 11 | 12 | @OptIn(ExperimentalMaterialApi::class) 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | setContent { 17 | BottomSheetNavigator { 18 | Navigator(BackScreen()) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.screen.Screen 5 | import cafe.adriel.voyager.core.screen.ScreenKey 6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 7 | import cafe.adriel.voyager.hilt.getScreenModel 8 | import cafe.adriel.voyager.navigator.LocalNavigator 9 | import cafe.adriel.voyager.navigator.currentOrThrow 10 | import cafe.adriel.voyager.sample.DetailsContent 11 | 12 | data class HiltDetailsScreen( 13 | val index: Int 14 | ) : Screen { 15 | override val key: ScreenKey = uniqueScreenKey 16 | 17 | @Composable 18 | override fun Content() { 19 | val navigator = LocalNavigator.currentOrThrow 20 | 21 | // Uncomment version below if you want keep using ViewModel instead of to convert it to ScreenModel 22 | // ViewModelProvider.Factory is not required. Until now Hilt has no support to Assisted Injection by default 23 | // val viewModel: HiltDetailsViewModel = getViewModel { factory -> factory.create(index) } 24 | 25 | // This version include more boilerplate because we are simulating support 26 | // to Assisted Injection using ScreenModel. See [HiltListScreen] for a simple version 27 | val viewModel = getScreenModel { factory -> 28 | factory.create(index) 29 | } 30 | 31 | DetailsContent(viewModel, "Item #${viewModel.index}", navigator::pop) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.hilt.ScreenModelFactory 5 | import dagger.assisted.Assisted 6 | import dagger.assisted.AssistedFactory 7 | import dagger.assisted.AssistedInject 8 | 9 | // Working with Assisted Injection here to simulate a custom param in the constructor 10 | class HiltDetailsScreenModel @AssistedInject constructor( 11 | @Assisted val index: Int 12 | ) : ScreenModel { 13 | 14 | @AssistedFactory 15 | interface Factory : ScreenModelFactory { 16 | fun create(index: Int): HiltDetailsScreenModel 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.assisted.Assisted 5 | import dagger.assisted.AssistedFactory 6 | import dagger.assisted.AssistedInject 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | 9 | @HiltViewModel(assistedFactory = HiltDetailsViewModel.Factory::class) 10 | class HiltDetailsViewModel @AssistedInject constructor( 11 | @Assisted val index: Int 12 | ) : ViewModel() { 13 | 14 | @AssistedFactory 15 | interface Factory { 16 | fun create(index: Int): HiltDetailsViewModel 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.screen.Screen 5 | import cafe.adriel.voyager.hilt.getViewModel 6 | import cafe.adriel.voyager.navigator.LocalNavigator 7 | import cafe.adriel.voyager.navigator.currentOrThrow 8 | import cafe.adriel.voyager.sample.ListContent 9 | 10 | class HiltListScreen : Screen { 11 | 12 | @Composable 13 | override fun Content() { 14 | val navigator = LocalNavigator.currentOrThrow 15 | val viewModel: HiltListViewModel = getViewModel() 16 | 17 | // Uncomment version below if you want to use ScreenModel 18 | // val viewModel: HiltListScreenModel = getScreenModel() 19 | 20 | ListContent(viewModel.items, onClick = { index -> navigator.push(HiltDetailsScreen(index)) }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.sample.sampleItems 5 | import javax.inject.Inject 6 | 7 | class HiltListScreenModel @Inject constructor() : ScreenModel { 8 | 9 | val items = sampleItems 10 | } 11 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListViewModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import cafe.adriel.voyager.sample.sampleItems 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import javax.inject.Inject 8 | 9 | @HiltViewModel 10 | class HiltListViewModel @Inject constructor( 11 | private val handle: SavedStateHandle 12 | ) : ViewModel() { 13 | 14 | init { 15 | if (handle.get>("items").isNullOrEmpty()) { 16 | handle["items"] = sampleItems 17 | } 18 | } 19 | 20 | val items: List 21 | get() = handle["items"] ?: error("Items not found") 22 | } 23 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltMainActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | import dagger.hilt.android.AndroidEntryPoint 8 | 9 | @AndroidEntryPoint 10 | class HiltMainActivity : ComponentActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | 15 | setContent { 16 | Navigator(HiltListScreen()) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltModule.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.hiltIntegration 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.hilt.ScreenModelFactory 5 | import cafe.adriel.voyager.hilt.ScreenModelFactoryKey 6 | import cafe.adriel.voyager.hilt.ScreenModelKey 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.components.ActivityComponent 11 | import dagger.multibindings.IntoMap 12 | 13 | @Module 14 | @InstallIn(ActivityComponent::class) 15 | abstract class HiltModule { 16 | @Binds 17 | @IntoMap 18 | @ScreenModelKey(HiltListScreenModel::class) 19 | abstract fun bindHiltScreenModel(hiltListScreenModel: HiltListScreenModel): ScreenModel 20 | 21 | @Binds 22 | @IntoMap 23 | @ScreenModelFactoryKey(HiltDetailsScreenModel.Factory::class) 24 | abstract fun bindHiltDetailsScreenModelFactory( 25 | hiltDetailsScreenModelFactory: HiltDetailsScreenModel.Factory 26 | ): ScreenModelFactory 27 | } 28 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinIntegrationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.kodeinIntegration 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class KodeinIntegrationActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(KodeinScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScopedDependencySample.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.kodeinIntegration 2 | 3 | import org.kodein.di.bindings.ScopeCloseable 4 | 5 | data class KodeinScopedDependencySample( 6 | val screenKey: String 7 | ) : ScopeCloseable { 8 | override fun close() { 9 | println("Being disposed") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.kodeinIntegration 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | import cafe.adriel.voyager.core.screen.Screen 7 | import cafe.adriel.voyager.core.screen.ScreenKey 8 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 9 | import cafe.adriel.voyager.kodein.ScreenContext 10 | import cafe.adriel.voyager.kodein.rememberScreenModel 11 | import cafe.adriel.voyager.sample.ListContent 12 | import org.kodein.di.compose.localDI 13 | import org.kodein.di.instance 14 | import org.kodein.di.on 15 | 16 | class KodeinScreen : Screen { 17 | 18 | override val key: ScreenKey = uniqueScreenKey 19 | 20 | @Composable 21 | override fun Content() { 22 | val screenModel = rememberScreenModel() 23 | val scopedDependency by localDI().on(ScreenContext(this)).instance() 24 | 25 | Column { 26 | Text(scopedDependency.toString()) 27 | ListContent(screenModel.items) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.kodeinIntegration 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.sample.sampleItems 5 | 6 | class KodeinScreenModel : ScreenModel { 7 | 8 | val items = sampleItems 9 | } 10 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinIntegrationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.koinIntegration 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class KoinIntegrationActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(KoinScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.koinIntegration 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.screen.Screen 5 | import cafe.adriel.voyager.core.screen.ScreenKey 6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 7 | import cafe.adriel.voyager.koin.getScreenModel 8 | import cafe.adriel.voyager.sample.ListContent 9 | 10 | class KoinScreen : Screen { 11 | 12 | override val key: ScreenKey = uniqueScreenKey 13 | 14 | @Composable 15 | override fun Content() { 16 | val screenModel = getScreenModel() 17 | 18 | ListContent(screenModel.items) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.koinIntegration 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.sample.sampleItems 5 | 6 | class KoinScreenModel : ScreenModel { 7 | 8 | val items = sampleItems 9 | } 10 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataIntegrationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.liveDataIntegration 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class LiveDataIntegrationActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(LiveDataScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.liveDataIntegration 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.currentCompositeKeyHash 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.livedata.observeAsState 8 | import cafe.adriel.voyager.core.model.rememberScreenModel 9 | import cafe.adriel.voyager.core.screen.Screen 10 | import cafe.adriel.voyager.core.screen.ScreenKey 11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 12 | import cafe.adriel.voyager.sample.ListContent 13 | import cafe.adriel.voyager.sample.LoadingContent 14 | 15 | class LiveDataScreen : Screen { 16 | 17 | override val key: ScreenKey = uniqueScreenKey 18 | 19 | @Composable 20 | override fun Content() { 21 | val screenModel = rememberScreenModel { LiveDataScreenModel() } 22 | val state by screenModel.state.observeAsState() 23 | 24 | when (val result = state) { 25 | is LiveDataScreenModel.State.Loading -> LoadingContent() 26 | is LiveDataScreenModel.State.Result -> ListContent(result.items) 27 | else -> Unit 28 | } 29 | 30 | LaunchedEffect(currentCompositeKeyHash) { 31 | screenModel.getItems() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.liveDataIntegration 2 | 3 | import cafe.adriel.voyager.core.model.screenModelScope 4 | import cafe.adriel.voyager.livedata.LiveScreenModel 5 | import cafe.adriel.voyager.sample.sampleItems 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | 9 | class LiveDataScreenModel : LiveScreenModel(State.Loading) { 10 | 11 | sealed class State { 12 | object Loading : State() 13 | data class Result(val items: List) : State() 14 | } 15 | 16 | private val items = sampleItems 17 | 18 | fun getItems() { 19 | screenModelScope.launch { 20 | delay(1_000) 21 | mutableState.postValue(State.Result(items)) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/parcelableScreen/ParcelableActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.parcelableScreen 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import cafe.adriel.voyager.navigator.Navigator 8 | 9 | class ParcelableActivity : ComponentActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | 14 | setContent { 15 | Navigator( 16 | screen = SampleParcelableScreen(parcelable = ParcelableContent(index = 0)), 17 | onBackPressed = { currentScreen -> 18 | Log.d("Navigator", "Pop screen #${(currentScreen as SampleParcelableScreen).parcelable.index}") 19 | true 20 | } 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaIntegrationActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.rxJavaIntegration 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class RxJavaIntegrationActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(RxJavaScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.rxJavaIntegration 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.currentCompositeKeyHash 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.rxjava3.subscribeAsState 8 | import cafe.adriel.voyager.core.model.rememberScreenModel 9 | import cafe.adriel.voyager.core.screen.Screen 10 | import cafe.adriel.voyager.core.screen.ScreenKey 11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 12 | import cafe.adriel.voyager.sample.ListContent 13 | import cafe.adriel.voyager.sample.LoadingContent 14 | 15 | class RxJavaScreen : Screen { 16 | 17 | override val key: ScreenKey = uniqueScreenKey 18 | 19 | @Composable 20 | override fun Content() { 21 | val screenModel = rememberScreenModel { RxJavaScreenModel() } 22 | val state by screenModel.state.subscribeAsState(initial = RxJavaScreenModel.State.Loading) 23 | 24 | when (val result = state) { 25 | is RxJavaScreenModel.State.Loading -> LoadingContent() 26 | is RxJavaScreenModel.State.Result -> ListContent(result.items) 27 | } 28 | 29 | LaunchedEffect(currentCompositeKeyHash) { 30 | screenModel.getItems() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.rxJavaIntegration 2 | 3 | import cafe.adriel.voyager.rxjava.RxScreenModel 4 | import cafe.adriel.voyager.rxjava.disposables 5 | import cafe.adriel.voyager.sample.sampleItems 6 | import io.reactivex.rxjava3.core.Single 7 | import java.util.concurrent.TimeUnit 8 | 9 | class RxJavaScreenModel : RxScreenModel() { 10 | 11 | sealed class State { 12 | object Loading : State() 13 | data class Result(val items: List) : State() 14 | } 15 | 16 | private val items = sampleItems 17 | 18 | fun getItems() { 19 | Single 20 | .just(items) 21 | .delay(1_000, TimeUnit.MILLISECONDS) 22 | .doOnSubscribe { mutableState.onNext(State.Loading) } 23 | .subscribe { items -> mutableState.onNext(State.Result(items)) } 24 | .let(disposables::add) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/DetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.screenModel 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.collectAsState 6 | import androidx.compose.runtime.currentCompositeKeyHash 7 | import androidx.compose.runtime.getValue 8 | import cafe.adriel.voyager.core.model.rememberScreenModel 9 | import cafe.adriel.voyager.core.screen.Screen 10 | import cafe.adriel.voyager.core.screen.ScreenKey 11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 12 | import cafe.adriel.voyager.navigator.LocalNavigator 13 | import cafe.adriel.voyager.navigator.currentOrThrow 14 | import cafe.adriel.voyager.sample.DetailsContent 15 | import cafe.adriel.voyager.sample.LoadingContent 16 | 17 | data class DetailsScreen( 18 | val index: Int 19 | ) : Screen { 20 | 21 | override val key: ScreenKey = uniqueScreenKey 22 | 23 | @Composable 24 | override fun Content() { 25 | val navigator = LocalNavigator.currentOrThrow 26 | val screenModel = rememberScreenModel { DetailsScreenModel(index) } 27 | val state by screenModel.state.collectAsState() 28 | 29 | when (val result = state) { 30 | is DetailsScreenModel.State.Loading -> LoadingContent() 31 | is DetailsScreenModel.State.Result -> DetailsContent(screenModel, result.item, navigator::pop) 32 | } 33 | 34 | LaunchedEffect(currentCompositeKeyHash) { 35 | screenModel.getItem(index) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/DetailsScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.screenModel 2 | 3 | import cafe.adriel.voyager.core.model.StateScreenModel 4 | import cafe.adriel.voyager.core.model.screenModelScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.launch 7 | 8 | class DetailsScreenModel( 9 | val index: Int 10 | ) : StateScreenModel(State.Loading) { 11 | 12 | sealed class State { 13 | object Loading : State() 14 | data class Result(val item: String) : State() 15 | } 16 | 17 | fun getItem(index: Int) { 18 | screenModelScope.launch { 19 | delay(1_000) 20 | mutableState.value = State.Result("Item #$index") 21 | } 22 | } 23 | 24 | override fun onDispose() { 25 | println("ScreenModel: dispose details") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ListScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.screenModel 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.model.rememberScreenModel 5 | import cafe.adriel.voyager.core.screen.Screen 6 | import cafe.adriel.voyager.core.screen.ScreenKey 7 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 8 | import cafe.adriel.voyager.navigator.LocalNavigator 9 | import cafe.adriel.voyager.navigator.currentOrThrow 10 | import cafe.adriel.voyager.sample.ListContent 11 | 12 | class ListScreen : Screen { 13 | 14 | override val key: ScreenKey = uniqueScreenKey 15 | 16 | @Composable 17 | override fun Content() { 18 | val navigator = LocalNavigator.currentOrThrow 19 | val screenModel = rememberScreenModel { ListScreenModel() } 20 | 21 | ListContent(screenModel.items, onClick = { index -> navigator.push(DetailsScreen(index)) }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ListScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.screenModel 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.sample.sampleItems 5 | 6 | class ListScreenModel : ScreenModel { 7 | 8 | val items = sampleItems 9 | 10 | override fun onDispose() { 11 | println("ScreenModel: dispose list") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ScreenModelActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.screenModel 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | 8 | class ScreenModelActivity : ComponentActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setContent { 14 | Navigator(ListScreen()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/FavoritesTab.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.tabNavigation.tabs 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Favorite 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter 8 | import cafe.adriel.voyager.navigator.tab.Tab 9 | import cafe.adriel.voyager.navigator.tab.TabOptions 10 | 11 | object FavoritesTab : Tab { 12 | 13 | override val options: TabOptions 14 | @Composable 15 | get() { 16 | val icon = rememberVectorPainter(Icons.Default.Favorite) 17 | 18 | return remember { 19 | TabOptions( 20 | index = 1u, 21 | title = "Favorites", 22 | icon = icon 23 | ) 24 | } 25 | } 26 | 27 | @Composable 28 | override fun Content() { 29 | TabContent() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/HomeTab.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.tabNavigation.tabs 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Home 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter 8 | import cafe.adriel.voyager.navigator.tab.Tab 9 | import cafe.adriel.voyager.navigator.tab.TabOptions 10 | 11 | object HomeTab : Tab { 12 | 13 | override val options: TabOptions 14 | @Composable 15 | get() { 16 | val icon = rememberVectorPainter(Icons.Default.Home) 17 | 18 | return remember { 19 | TabOptions( 20 | index = 0u, 21 | title = "Home", 22 | icon = icon 23 | ) 24 | } 25 | } 26 | 27 | @Composable 28 | override fun Content() { 29 | TabContent() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/ProfileTab.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.tabNavigation.tabs 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Person 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter 8 | import cafe.adriel.voyager.navigator.tab.Tab 9 | import cafe.adriel.voyager.navigator.tab.TabOptions 10 | 11 | object ProfileTab : Tab { 12 | 13 | override val options: TabOptions 14 | @Composable 15 | get() { 16 | val icon = rememberVectorPainter(Icons.Default.Person) 17 | 18 | return remember { 19 | TabOptions( 20 | index = 2u, 21 | title = "Profile", 22 | icon = icon 23 | ) 24 | } 25 | } 26 | 27 | @Composable 28 | override fun Content() { 29 | TabContent() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/android/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /samples/android/src/main/res/layout/activity_legacy.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 24 | 25 | 31 | 32 | 33 | 34 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Voyager 3 | This is a legacy content 4 | 5 | -------------------------------------------------------------------------------- /samples/multi-module/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/multi-module/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /debug 3 | /release -------------------------------------------------------------------------------- /samples/multi-module/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | } 5 | 6 | setupModuleForAndroidxCompose( 7 | withKotlinExplicitMode = false 8 | ) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.sample.multimodule" 12 | defaultConfig { 13 | applicationId = "cafe.adriel.voyager.sample.multimodule" 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(projects.voyagerNavigator) 19 | 20 | implementation(projects.samples.multiModule.featureHome) 21 | implementation(projects.samples.multiModule.featurePosts) 22 | 23 | implementation(libs.appCompat) 24 | implementation(libs.compose.compiler) 25 | implementation(libs.compose.runtime) 26 | implementation(libs.compose.activity) 27 | implementation(libs.compose.material) 28 | 29 | debugImplementation(libs.leakCanary) 30 | } 31 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/java/cafe/adriel/voyager/sample/multimodule/SampleActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import cafe.adriel.voyager.navigator.Navigator 7 | import cafe.adriel.voyager.sample.multimodule.home.HomeScreen 8 | 9 | class SampleActivity : ComponentActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | 14 | setContent { 15 | Navigator(HomeScreen()) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/java/cafe/adriel/voyager/sample/multimodule/SampleApp.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule 2 | 3 | import android.app.Application 4 | import cafe.adriel.voyager.core.registry.ScreenRegistry 5 | import cafe.adriel.voyager.sample.multimodule.posts.featurePostsScreenModule 6 | 7 | class SampleApp : Application() { 8 | 9 | override fun onCreate() { 10 | super.onCreate() 11 | 12 | ScreenRegistry { 13 | featurePostsScreenModule() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multi-module/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Voyager MultiModule 3 | 4 | -------------------------------------------------------------------------------- /samples/multi-module/feature-home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/multi-module/feature-home/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | } 5 | 6 | setupModuleForAndroidxCompose( 7 | withKotlinExplicitMode = false 8 | ) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.sample.multimodule.home" 12 | } 13 | 14 | dependencies { 15 | implementation(projects.voyagerNavigator) 16 | 17 | implementation(projects.samples.multiModule.navigation) 18 | 19 | implementation(libs.appCompat) 20 | implementation(libs.compose.activity) 21 | implementation(libs.compose.material) 22 | } 23 | -------------------------------------------------------------------------------- /samples/multi-module/feature-home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | } 5 | 6 | setupModuleForAndroidxCompose( 7 | withKotlinExplicitMode = false 8 | ) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.sample.multimodule.posts" 12 | } 13 | 14 | dependencies { 15 | implementation(projects.voyagerScreenmodel) 16 | implementation(projects.voyagerNavigator) 17 | 18 | implementation(projects.samples.multiModule.navigation) 19 | 20 | implementation(libs.appCompat) 21 | implementation(libs.compose.activity) 22 | implementation(libs.compose.material) 23 | } 24 | -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule.posts 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.material.Button 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.unit.dp 15 | import cafe.adriel.voyager.core.screen.Screen 16 | import cafe.adriel.voyager.navigator.LocalNavigator 17 | import cafe.adriel.voyager.navigator.currentOrThrow 18 | 19 | data class DetailsScreen( 20 | val id: String 21 | ) : Screen { 22 | 23 | @Composable 24 | override fun Content() { 25 | val navigator = LocalNavigator.currentOrThrow 26 | 27 | Column( 28 | verticalArrangement = Arrangement.Center, 29 | horizontalAlignment = Alignment.CenterHorizontally, 30 | modifier = Modifier.fillMaxSize() 31 | ) { 32 | Text( 33 | text = "Post Details", 34 | style = MaterialTheme.typography.h5 35 | ) 36 | 37 | Spacer(modifier = Modifier.height(16.dp)) 38 | 39 | Text( 40 | text = "ID: $id", 41 | style = MaterialTheme.typography.body1 42 | ) 43 | 44 | Spacer(modifier = Modifier.height(16.dp)) 45 | 46 | Button( 47 | onClick = { navigator.pop() } 48 | ) { 49 | Text( 50 | text = "Return", 51 | style = MaterialTheme.typography.button 52 | ) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule.posts 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.material.Button 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.unit.dp 15 | import cafe.adriel.voyager.core.screen.Screen 16 | import cafe.adriel.voyager.navigator.LocalNavigator 17 | import cafe.adriel.voyager.navigator.currentOrThrow 18 | 19 | class ListScreen : Screen { 20 | 21 | @Composable 22 | override fun Content() { 23 | val navigator = LocalNavigator.currentOrThrow 24 | 25 | Column( 26 | verticalArrangement = Arrangement.Center, 27 | horizontalAlignment = Alignment.CenterHorizontally, 28 | modifier = Modifier.fillMaxSize() 29 | ) { 30 | Text( 31 | text = "Post List", 32 | style = MaterialTheme.typography.h5 33 | ) 34 | 35 | Spacer(modifier = Modifier.height(16.dp)) 36 | 37 | Button( 38 | onClick = { navigator.pop() } 39 | ) { 40 | Text( 41 | text = "Return", 42 | style = MaterialTheme.typography.button 43 | ) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ScreenModule.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule.posts 2 | 3 | import cafe.adriel.voyager.core.registry.screenModule 4 | import cafe.adriel.voyager.sample.multimodule.navigation.SharedScreen 5 | 6 | val featurePostsScreenModule = screenModule { 7 | register { 8 | ListScreen() 9 | } 10 | register { provider -> 11 | DetailsScreen(id = provider.id) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/multi-module/navigation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/multi-module/navigation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | } 5 | 6 | setupModuleForAndroidxCompose( 7 | withKotlinExplicitMode = false 8 | ) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.sample.multimodule.navigation" 12 | } 13 | 14 | dependencies { 15 | implementation(projects.voyagerCore) 16 | 17 | implementation(libs.compose.compiler) 18 | implementation(libs.compose.runtime) 19 | } 20 | -------------------------------------------------------------------------------- /samples/multi-module/navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/multi-module/navigation/src/main/java/cafe/adriel/voyager/sample/multimodule/navigation/SharedScreen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multimodule.navigation 2 | 3 | import cafe.adriel.voyager.core.registry.ScreenProvider 4 | 5 | sealed class SharedScreen : ScreenProvider { 6 | object PostList : SharedScreen() 7 | data class PostDetails(val id: String) : SharedScreen() 8 | } 9 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=org.example.project.KotlinProject 3 | APP_NAME=KotlinProject -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gabriel.lopes.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gabriel.lopes.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp.xcodeproj/xcuserdata/gabriel.lopes.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iosApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 17 | } 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | CADisableMinimumFrameDurationOnPhone 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /samples/multiplatform-iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/multiplatform/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /debug 3 | /release -------------------------------------------------------------------------------- /samples/multiplatform/README.md: -------------------------------------------------------------------------------- 1 | # multiplatform sample 2 | 3 | ### Running iOS 4 | - Open Xcode project with: `open samples/multiplatform-iosApp/iosApp.xcodeproj` 5 | - Run/Build in Xcode 6 | 7 | ### Running MacOS Native app (Desktop using Kotlin Native) 8 | ```shell 9 | ./gradlew :samples:multiplatform:runNativeDebug 10 | ``` 11 | 12 | ### Running JVM Native app (Desktop) 13 | ```shell 14 | ./gradlew :samples:multiplatform:run 15 | ``` 16 | 17 | ### Running Web Compose Canvas 18 | ```shell 19 | ./gradlew :samples:multiplatform:jsBrowserDevelopmentRun 20 | ``` 21 | 22 | ### Building Android App 23 | ```shell 24 | ./gradlew :samples:multiplatform:assembleDebug 25 | ``` 26 | 27 | If you want to run Android sample in the emulator, you can open the project and run the application configuration `samples.multiplatform` on Android Studio. 28 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/kotlin/cafe/adriel/voyager/sample/multiplatform/SampleActivity.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multiplatform 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | public class SampleActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | 11 | setContent { 12 | SampleApplication() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/multiplatform/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Voyager 3 | 4 | -------------------------------------------------------------------------------- /samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/Application.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multiplatform 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.navigator.Navigator 5 | import cafe.adriel.voyager.transitions.SlideTransition 6 | 7 | @Composable 8 | public fun SampleApplication() { 9 | Navigator( 10 | screen = BasicNavigationScreen(index = 0), 11 | onBackPressed = { currentScreen -> 12 | println("Navigator: Pop screen #${(currentScreen as BasicNavigationScreen).index}") 13 | true 14 | } 15 | ) { navigator -> 16 | SlideTransition(navigator) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/multiplatform/src/desktopMain/kotlin/cafe/adriel/voyager/sample/multiplatform/App.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.sample.multiplatform 2 | 3 | import androidx.compose.ui.window.Window 4 | import androidx.compose.ui.window.application 5 | 6 | public fun main() { 7 | application { 8 | Window(onCloseRequest = ::exitApplication) { 9 | SampleApplication() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/multiplatform/src/iosMain/kotlin/MainViewController.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.ComposeUIViewController 2 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication 3 | 4 | fun MainViewController() = ComposeUIViewController { SampleApplication() } 5 | -------------------------------------------------------------------------------- /samples/multiplatform/src/jsMain/kotlin/main.js.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.Window 2 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication 3 | import org.jetbrains.skiko.wasm.onWasmReady 4 | 5 | fun main() { 6 | onWasmReady { 7 | Window("Voyager Sample") { 8 | SampleApplication() 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/multiplatform/src/jsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Voyager Sample - Compose Multiplatform 6 | 7 | 8 | 9 | 10 |

Voyager Sample - Compose Multiplatform

11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/multiplatform/src/jsMain/resources/style.css: -------------------------------------------------------------------------------- 1 | #root { 2 | width: 100%; 3 | height: 100vh; 4 | } 5 | 6 | #root > .compose-web-column > div { 7 | position: relative; 8 | } -------------------------------------------------------------------------------- /samples/multiplatform/src/macosMain/kotlin/main.macos.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.Window 2 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication 3 | import platform.AppKit.NSApp 4 | import platform.AppKit.NSApplication 5 | 6 | fun main() { 7 | NSApplication.sharedApplication() 8 | Window("VoyagerMultiplatform") { 9 | SampleApplication() 10 | } 11 | NSApp?.run() 12 | } 13 | -------------------------------------------------------------------------------- /samples/multiplatform/src/wasmJsMain/kotlin/main.wasmJs.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.CanvasBasedWindow 3 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication 4 | 5 | @OptIn(ExperimentalComposeUiApi::class) 6 | fun main() { 7 | CanvasBasedWindow(canvasElementId = "ComposeTarget") { SampleApplication() } 8 | } 9 | -------------------------------------------------------------------------------- /samples/multiplatform/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Compose App 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | dependencyResolutionManagement { 4 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 5 | repositories { 6 | google() 7 | mavenCentral() 8 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev" ) 9 | } 10 | } 11 | 12 | plugins { 13 | id("com.dropbox.focus") version "0.4.0" 14 | } 15 | 16 | configure { 17 | allSettingsFileName.set("includes.gradle.kts") 18 | focusFileName.set(".focus") 19 | } 20 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.navigator.bottomSheet" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerNavigator) 19 | compileOnly(compose.runtime) 20 | compileOnly(compose.material) 21 | } 22 | 23 | jvmTest.dependencies { 24 | implementation(libs.junit.api) 25 | runtimeOnly(libs.junit.engine) 26 | } 27 | 28 | androidMain.dependencies { 29 | implementation(libs.compose.activity) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-bottom-sheet-navigator/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerBottomSheetNavigator 2 | POM_ARTIFACT_ID=voyager-bottom-sheet-navigator -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.activity.compose.BackHandler 4 | import androidx.compose.runtime.Composable 5 | 6 | @Composable 7 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = 8 | BackHandler(enabled, onBack) 9 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/BottomSheetNavigatorBackHandler.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.compose.material.ExperimentalMaterialApi 4 | import androidx.compose.material.ModalBottomSheetState 5 | import androidx.compose.runtime.Composable 6 | import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator 7 | 8 | @Composable 9 | internal expect fun BackHandler(enabled: Boolean, onBack: () -> Unit) 10 | 11 | @ExperimentalMaterialApi 12 | @Composable 13 | internal fun BottomSheetNavigatorBackHandler( 14 | navigator: BottomSheetNavigator, 15 | sheetState: ModalBottomSheetState, 16 | hideOnBackPress: Boolean 17 | ) { 18 | BackHandler(enabled = sheetState.isVisible) { 19 | if (navigator.pop().not() && hideOnBackPress) { 20 | navigator.hide() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/commonWebMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.web.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit 7 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/desktopMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit 7 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/iosMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.uikit.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | // TODO: use ios backstack 6 | @Composable 7 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit 8 | -------------------------------------------------------------------------------- /voyager-bottom-sheet-navigator/src/macosMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.macos.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.bottomSheet.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit 7 | -------------------------------------------------------------------------------- /voyager-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | id("kotlinx-atomicfu") 7 | } 8 | 9 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 10 | 11 | android { 12 | namespace = "cafe.adriel.voyager.core" 13 | defaultConfig { 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | kotlin { 19 | sourceSets { 20 | commonMain.dependencies { 21 | compileOnly(compose.runtime) 22 | compileOnly(compose.runtimeSaveable) 23 | implementation(libs.coroutines.core) 24 | } 25 | jvmTest.dependencies { 26 | implementation(libs.junit.api) 27 | runtimeOnly(libs.junit.engine) 28 | } 29 | androidMain.dependencies { 30 | implementation(libs.compose.activity) 31 | 32 | implementation(libs.lifecycle.runtime) 33 | implementation(libs.lifecycle.savedState) 34 | implementation(libs.lifecycle.viewModelKtx) 35 | implementation(libs.lifecycle.viewModelCompose) 36 | } 37 | val commonWebMain by getting { 38 | dependencies { 39 | implementation(libs.multiplatformUuid) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /voyager-core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keepclassmembers class * implements cafe.adriel.voyager.core.screen.Screen { 2 | public getKey(); 3 | } -------------------------------------------------------------------------------- /voyager-core/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerCore 2 | POM_ARTIFACT_ID=voyager-core -------------------------------------------------------------------------------- /voyager-core/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.android.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | @InternalVoyagerApi 8 | public actual val PlatformMainDispatcher: CoroutineDispatcher 9 | get() = Dispatchers.Main.immediate 10 | -------------------------------------------------------------------------------- /voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.ContextWrapper 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.Stable 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.platform.LocalContext 10 | 11 | private tailrec fun Context.getActivity(): Activity? = when (this) { 12 | is Activity -> this 13 | is ContextWrapper -> baseContext.getActivity() 14 | else -> null 15 | } 16 | 17 | @Composable 18 | internal actual fun getConfigurationChecker(): ConfigurationChecker { 19 | val context = LocalContext.current 20 | return remember(context) { ConfigurationChecker(context.getActivity()) } 21 | } 22 | 23 | @Stable 24 | internal actual class ConfigurationChecker(private val activity: Activity?) { 25 | actual fun isChangingConfigurations(): Boolean { 26 | return activity?.isChangingConfigurations ?: false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/annotation/InternalVoyagerApi.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.annotation 2 | 3 | @Target( 4 | allowedTargets = [ 5 | AnnotationTarget.CLASS, 6 | AnnotationTarget.PROPERTY, 7 | AnnotationTarget.FUNCTION, 8 | AnnotationTarget.TYPEALIAS, 9 | AnnotationTarget.CONSTRUCTOR 10 | ] 11 | ) 12 | @RequiresOptIn( 13 | level = RequiresOptIn.Level.ERROR, 14 | message = "This API is Voyager Internal. It may be changed in the future without notice." 15 | ) 16 | public annotation class InternalVoyagerApi 17 | 18 | @Target( 19 | allowedTargets = [ 20 | AnnotationTarget.CLASS, 21 | AnnotationTarget.PROPERTY, 22 | AnnotationTarget.FUNCTION, 23 | AnnotationTarget.TYPEALIAS, 24 | AnnotationTarget.CONSTRUCTOR 25 | ] 26 | ) 27 | @RequiresOptIn( 28 | level = RequiresOptIn.Level.ERROR, 29 | message = "This API is experimental. It may be changed in the future without notice." 30 | ) 31 | public annotation class ExperimentalVoyagerApi 32 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/AtomicInt32.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | public expect class AtomicInt32(initialValue: Int) { 4 | public fun getAndIncrement(): Int 5 | } 6 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | 6 | @InternalVoyagerApi 7 | public expect val PlatformMainDispatcher: CoroutineDispatcher 8 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeList.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | public expect class ThreadSafeList() : MutableList 4 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeMap.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | public expect class ThreadSafeMap() : MutableMap 4 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeSet.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | public expect class ThreadSafeSet() : MutableSet 4 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.NonRestartableComposable 5 | import androidx.compose.runtime.Stable 6 | 7 | @Composable 8 | @NonRestartableComposable 9 | internal expect fun getConfigurationChecker(): ConfigurationChecker 10 | 11 | @Stable 12 | internal expect class ConfigurationChecker { 13 | fun isChangingConfigurations(): Boolean 14 | } 15 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/LifecycleEffectStore.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap 4 | import cafe.adriel.voyager.core.concurrent.ThreadSafeSet 5 | import cafe.adriel.voyager.core.screen.Screen 6 | import cafe.adriel.voyager.core.screen.ScreenKey 7 | 8 | internal object LifecycleEffectStore : ScreenDisposable { 9 | private val executedLifecycles = ThreadSafeMap>() 10 | 11 | fun store(screen: Screen, effectKey: String): LifecycleEffectOnceScope { 12 | val set = executedLifecycles.getOrPut(screen.key) { ThreadSafeSet() } 13 | val scope = LifecycleEffectOnceScope(uniqueKey = effectKey, set.size + 1) 14 | set.add(scope) 15 | 16 | return scope 17 | } 18 | 19 | fun hasExecuted(screen: Screen, effectKey: String): Boolean = 20 | executedLifecycles.get(screen.key)?.any { it.uniqueKey == effectKey } == true 21 | 22 | override fun onDispose(screen: Screen) { 23 | val scopes = executedLifecycles.remove(screen.key) 24 | scopes?.sortedBy { it.registerOrderIndex }?.reversed()?.forEach { scope -> 25 | scope.onDisposed?.invoke() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/NavigatorScreenLifecycle.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.ProvidableCompositionLocal 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | public val LocalNavigatorScreenLifecycleProvider: ProvidableCompositionLocal = 8 | staticCompositionLocalOf { null } 9 | 10 | /** 11 | * Can provides a list of ScreenLifecycleOwner for each Screen in the Navigator stack. 12 | */ 13 | public interface NavigatorScreenLifecycleProvider { 14 | 15 | public fun provide(screen: Screen): List 16 | } 17 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | public interface ScreenLifecycleOwner : ScreenLifecycleContentProvider, ScreenDisposable 8 | 9 | public interface ScreenLifecycleContentProvider { 10 | /** 11 | * Called before rendering the Screen Content. 12 | * 13 | * IMPORTANT: This is only called when ScreenLifecycleOwner is provided by [ScreenLifecycleProvider] or [NavigatorScreenLifecycleProvider]. 14 | */ 15 | @Composable 16 | public fun ProvideBeforeScreenContent( 17 | provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit, 18 | content: @Composable () -> Unit 19 | ): Unit = content() 20 | } 21 | 22 | public interface ScreenDisposable { 23 | /** 24 | * Called on the Screen leaves the stack. 25 | */ 26 | public fun onDispose(screen: Screen) {} 27 | } 28 | 29 | @InternalVoyagerApi 30 | public object DefaultScreenLifecycleOwner : ScreenLifecycleOwner 31 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/Serializable.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | /** 4 | * Multiplatform reference to Java `Serializable` interface. 5 | * 6 | * Any object that will be pass to a Screen as parameter, on Android target, requires to be serializable in a 7 | * Bundle, so if your project is multiplatform and targeting Android, to prevent State Restoration issues on Android 8 | * the easiest way is to apply this interface to your Models. 9 | */ 10 | public expect interface JavaSerializable 11 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/platform/KClassEx.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.platform 2 | 3 | import kotlin.reflect.KClass 4 | 5 | public expect val KClass<*>.multiplatformName: String? 6 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenModule.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.registry 2 | 3 | private typealias ScreenModule = ScreenRegistry.() -> Unit 4 | 5 | public fun screenModule(block: ScreenModule): ScreenModule = 6 | { block() } 7 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenProvider.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.registry 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | @Composable 8 | public inline fun rememberScreen(provider: T): Screen = 9 | remember(provider) { 10 | ScreenRegistry.get(provider) 11 | } 12 | 13 | public interface ScreenProvider 14 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenRegistry.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.registry 2 | 3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap 4 | import cafe.adriel.voyager.core.platform.multiplatformName 5 | import cafe.adriel.voyager.core.screen.Screen 6 | import kotlin.reflect.KClass 7 | 8 | private typealias ProviderKey = KClass 9 | 10 | private typealias ScreenFactory = (ScreenProvider) -> Screen 11 | 12 | public object ScreenRegistry { 13 | 14 | @PublishedApi 15 | internal val factories: ThreadSafeMap = ThreadSafeMap() 16 | 17 | public operator fun invoke(block: ScreenRegistry.() -> Unit) { 18 | this.block() 19 | } 20 | 21 | public inline fun register(noinline factory: (T) -> Screen) { 22 | factories[T::class] = factory as ScreenFactory 23 | } 24 | 25 | public fun get(provider: ScreenProvider): Screen { 26 | val factory = factories[provider::class] 27 | ?: error("ScreenProvider not registered: ${provider::class.multiplatformName}") 28 | return factory(provider) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/screen/Screen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.platform.multiplatformName 5 | 6 | public expect interface Screen { 7 | 8 | public open val key: ScreenKey 9 | 10 | @Composable 11 | public fun Content() 12 | } 13 | 14 | internal fun Screen.commonKeyGeneration() = 15 | this::class.multiplatformName ?: error("Default ScreenKey not found, please provide your own key") 16 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/screen/ScreenKey.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | public typealias ScreenKey = String 4 | 5 | internal expect fun randomUuid(): String 6 | 7 | public val Screen.uniqueScreenKey: ScreenKey 8 | get() = "Screen#${randomUuid()}" 9 | -------------------------------------------------------------------------------- /voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/stack/Stack.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.stack 2 | 3 | public inline fun Stack.popUntil(): Boolean = 4 | popUntil { item -> item is I } 5 | 6 | public enum class StackEvent { 7 | Push, 8 | Replace, 9 | Pop, 10 | Idle 11 | } 12 | 13 | public interface Stack { 14 | 15 | public val items: List 16 | 17 | public val lastEvent: StackEvent 18 | 19 | public val lastItemOrNull: Item? 20 | 21 | public val size: Int 22 | 23 | public val isEmpty: Boolean 24 | 25 | public val canPop: Boolean 26 | 27 | public infix fun push(item: Item) 28 | 29 | public infix fun push(items: List) 30 | 31 | public infix fun replace(item: Item) 32 | 33 | public infix fun replaceAll(item: Item) 34 | 35 | public infix fun replaceAll(items: List) 36 | 37 | public fun pop(): Boolean 38 | 39 | public fun popAll() 40 | 41 | public infix fun popUntil(predicate: (Item) -> Boolean): Boolean 42 | 43 | public operator fun plusAssign(item: Item) 44 | 45 | public operator fun plusAssign(items: List) 46 | 47 | public fun clearEvent() 48 | } 49 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/AtomicInt32.js.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.atomic 4 | 5 | public actual class AtomicInt32 actual constructor(initialValue: Int) { 6 | private val delegate = atomic(initialValue) 7 | public actual fun getAndIncrement(): Int { 8 | return delegate.incrementAndGet() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/PlatformDispatcher.js.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | @InternalVoyagerApi 8 | public actual val PlatformMainDispatcher: CoroutineDispatcher 9 | get() = Dispatchers.Default 10 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMap.js.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.locks.SynchronizedObject 4 | import kotlinx.atomicfu.locks.synchronized 5 | 6 | public actual class ThreadSafeMap( 7 | private val delegate: MutableMap 8 | ) : MutableMap { 9 | public actual constructor() : this(delegate = mutableMapOf()) 10 | private val syncObject = SynchronizedObject() 11 | 12 | override val size: Int 13 | get() = synchronized(syncObject) { delegate.size } 14 | 15 | override fun containsKey(key: K): Boolean { 16 | return synchronized(syncObject) { delegate.containsKey(key) } 17 | } 18 | 19 | override fun containsValue(value: V): Boolean { 20 | return synchronized(syncObject) { delegate.containsValue(value) } 21 | } 22 | 23 | override fun get(key: K): V? { 24 | return synchronized(syncObject) { delegate[key] } 25 | } 26 | 27 | override fun isEmpty(): Boolean { 28 | return synchronized(syncObject) { delegate.isEmpty() } 29 | } 30 | 31 | override val entries: MutableSet> 32 | get() = delegate.entries 33 | override val keys: MutableSet 34 | get() = delegate.keys 35 | override val values: MutableCollection 36 | get() = delegate.values 37 | 38 | override fun clear() { 39 | synchronized(syncObject) { delegate.clear() } 40 | } 41 | 42 | override fun put(key: K, value: V): V? { 43 | return synchronized(syncObject) { delegate.put(key, value) } 44 | } 45 | 46 | override fun putAll(from: Map) { 47 | synchronized(syncObject) { delegate.putAll(from) } 48 | } 49 | 50 | override fun remove(key: K): V? { 51 | return synchronized(syncObject) { delegate.remove(key) } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeSet.js.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.locks.SynchronizedObject 4 | import kotlinx.atomicfu.locks.synchronized 5 | 6 | public actual class ThreadSafeSet( 7 | private val delegate: MutableSet 8 | ) : MutableSet { 9 | public actual constructor() : this(delegate = mutableSetOf()) 10 | private val syncObject = SynchronizedObject() 11 | 12 | override val size: Int 13 | get() = delegate.size 14 | 15 | override fun contains(element: T): Boolean { 16 | return synchronized(syncObject) { delegate.contains(element) } 17 | } 18 | 19 | override fun containsAll(elements: Collection): Boolean { 20 | return synchronized(syncObject) { delegate.containsAll(elements) } 21 | } 22 | 23 | override fun isEmpty(): Boolean { 24 | return synchronized(syncObject) { delegate.isEmpty() } 25 | } 26 | 27 | override fun iterator(): MutableIterator { 28 | return synchronized(syncObject) { delegate.iterator() } 29 | } 30 | 31 | override fun add(element: T): Boolean { 32 | return synchronized(syncObject) { delegate.add(element) } 33 | } 34 | 35 | override fun addAll(elements: Collection): Boolean { 36 | return synchronized(syncObject) { delegate.addAll(elements) } 37 | } 38 | 39 | override fun clear() { 40 | return synchronized(syncObject) { delegate.clear() } 41 | } 42 | 43 | override fun remove(element: T): Boolean { 44 | return synchronized(syncObject) { delegate.remove(element) } 45 | } 46 | 47 | override fun removeAll(elements: Collection): Boolean { 48 | return synchronized(syncObject) { delegate.removeAll(elements) } 49 | } 50 | 51 | override fun retainAll(elements: Collection): Boolean { 52 | return synchronized(syncObject) { delegate.retainAll(elements) } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/lifecycle/ConfigurationChecker.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.remember 6 | 7 | private val configurationChecker = ConfigurationChecker() 8 | 9 | @Composable 10 | internal actual fun getConfigurationChecker(): ConfigurationChecker { 11 | return remember { configurationChecker } 12 | } 13 | 14 | @Stable 15 | internal actual class ConfigurationChecker { 16 | actual fun isChangingConfigurations(): Boolean = false 17 | } 18 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/lifecycle/Serializable.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | public actual interface JavaSerializable 4 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/platform/KClassEx.js.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.platform 2 | 3 | import kotlin.reflect.KClass 4 | 5 | public actual val KClass<*>.multiplatformName: String? get() = simpleName 6 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/screen/Screen.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | public actual interface Screen { 6 | public actual val key: ScreenKey 7 | get() = commonKeyGeneration() 8 | 9 | @Composable 10 | public actual fun Content() 11 | } 12 | -------------------------------------------------------------------------------- /voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/screen/ScreenKey.web.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import com.benasher44.uuid.uuid4 4 | 5 | internal actual fun randomUuid(): String = uuid4().toString() 6 | -------------------------------------------------------------------------------- /voyager-core/src/desktopMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.desktop.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | @InternalVoyagerApi 8 | public actual val PlatformMainDispatcher: CoroutineDispatcher 9 | get() = Dispatchers.Main.immediate 10 | -------------------------------------------------------------------------------- /voyager-core/src/desktopMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.remember 6 | 7 | private val configurationChecker = ConfigurationChecker() 8 | 9 | @Composable 10 | internal actual fun getConfigurationChecker(): ConfigurationChecker { 11 | return remember { configurationChecker } 12 | } 13 | 14 | @Stable 15 | internal actual class ConfigurationChecker { 16 | actual fun isChangingConfigurations(): Boolean = false 17 | } 18 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/concurrent/AtomicInt32.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | public actual typealias AtomicInt32 = java.util.concurrent.atomic.AtomicInteger 4 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeList.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import java.util.concurrent.CopyOnWriteArrayList 4 | 5 | public actual class ThreadSafeList : MutableList by CopyOnWriteArrayList() 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeMap.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import java.util.concurrent.ConcurrentHashMap 4 | 5 | public actual class ThreadSafeMap : MutableMap by ConcurrentHashMap() 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeSet.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import java.util.concurrent.CopyOnWriteArraySet 4 | 5 | public actual class ThreadSafeSet : MutableSet by CopyOnWriteArraySet() 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/lifecycle/Serializable.jvm.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import java.io.Serializable 4 | 5 | public actual interface JavaSerializable : Serializable 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/platform/KClassEx.jvm.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.platform 2 | 3 | import kotlin.reflect.KClass 4 | 5 | public actual val KClass<*>.multiplatformName: String? get() = qualifiedName 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/screen/Screen.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | import java.io.Serializable 5 | 6 | public actual interface Screen : Serializable { 7 | public actual val key: ScreenKey 8 | get() = commonKeyGeneration() 9 | 10 | @Composable 11 | public actual fun Content() 12 | } 13 | -------------------------------------------------------------------------------- /voyager-core/src/jvmMain/kotlin/cafe/adriel/voyager/core/screen/ScreenKey.jvm.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import java.util.UUID 4 | 5 | internal actual fun randomUuid(): String = UUID.randomUUID().toString() 6 | -------------------------------------------------------------------------------- /voyager-core/src/jvmTest/kotlin/cafe/adriel/voyager/core/utils/Quadruple.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.utils 2 | 3 | internal data class Quadruple( 4 | val first: A, 5 | val second: B, 6 | val third: C, 7 | val fourth: D 8 | ) 9 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/AtomicInt32.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.atomic 4 | 5 | public actual class AtomicInt32 actual constructor(initialValue: Int) { 6 | private val delegate = atomic(initialValue) 7 | public actual fun getAndIncrement(): Int { 8 | return delegate.incrementAndGet() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/PlatformDispatcher.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | @InternalVoyagerApi 8 | public actual val PlatformMainDispatcher: CoroutineDispatcher 9 | get() = Dispatchers.Main.immediate 10 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMutableCollection.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.locks.SynchronizedObject 4 | import kotlinx.atomicfu.locks.synchronized 5 | 6 | public open class ThreadSafeMutableCollectioninternal constructor( 7 | private val syncObject: SynchronizedObject, 8 | private val delegate: MutableCollection 9 | ) : MutableCollection { 10 | 11 | override val size: Int 12 | get() = synchronized(syncObject) { delegate.size } 13 | 14 | override fun contains(element: T): Boolean { 15 | return synchronized(syncObject) { delegate.contains(element) } 16 | } 17 | 18 | override fun containsAll(elements: Collection): Boolean { 19 | return synchronized(syncObject) { delegate.containsAll(elements) } 20 | } 21 | 22 | override fun isEmpty(): Boolean { 23 | return synchronized(syncObject) { delegate.isEmpty() } 24 | } 25 | 26 | override fun iterator(): MutableIterator { 27 | return synchronized(syncObject) { ThreadSafeMutableIterator(syncObject, delegate.iterator()) } 28 | } 29 | 30 | override fun add(element: T): Boolean { 31 | return synchronized(syncObject) { delegate.add(element) } 32 | } 33 | 34 | override fun addAll(elements: Collection): Boolean { 35 | return synchronized(syncObject) { delegate.addAll(elements) } 36 | } 37 | 38 | override fun clear() { 39 | return synchronized(syncObject) { delegate.clear() } 40 | } 41 | 42 | override fun remove(element: T): Boolean { 43 | return synchronized(syncObject) { delegate.remove(element) } 44 | } 45 | 46 | override fun removeAll(elements: Collection): Boolean { 47 | return synchronized(syncObject) { delegate.removeAll(elements) } 48 | } 49 | 50 | override fun retainAll(elements: Collection): Boolean { 51 | return synchronized(syncObject) { delegate.retainAll(elements) } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMutableIterator.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.locks.SynchronizedObject 4 | import kotlinx.atomicfu.locks.synchronized 5 | 6 | internal open class ThreadSafeMutableIterator( 7 | private val syncObject: SynchronizedObject, 8 | private val delegate: MutableIterator 9 | ) : MutableIterator { 10 | override fun hasNext(): Boolean = synchronized(syncObject) { delegate.hasNext() } 11 | 12 | override fun next(): E = synchronized(syncObject) { delegate.next() } 13 | 14 | override fun remove() { 15 | synchronized(syncObject) { delegate.remove() } 16 | } 17 | } 18 | 19 | internal class ThreadSafeMutableListIterator( 20 | private val syncObject: SynchronizedObject, 21 | private val delegate: MutableListIterator 22 | ) : ThreadSafeMutableIterator(syncObject, delegate), 23 | MutableListIterator { 24 | 25 | override fun hasPrevious(): Boolean = synchronized(syncObject) { delegate.hasPrevious() } 26 | 27 | override fun nextIndex(): Int = synchronized(syncObject) { delegate.nextIndex() } 28 | 29 | override fun previous(): E = synchronized(syncObject) { delegate.previous() } 30 | 31 | override fun previousIndex(): Int = synchronized(syncObject) { delegate.previousIndex() } 32 | 33 | override fun add(element: E) { 34 | synchronized(syncObject) { delegate.add(element) } 35 | } 36 | 37 | override fun set(element: E) { 38 | synchronized(syncObject) { delegate.set(element) } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeSet.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.concurrent 2 | 3 | import kotlinx.atomicfu.locks.SynchronizedObject 4 | 5 | public actual class ThreadSafeSet( 6 | syncObject: SynchronizedObject, 7 | delegate: MutableSet 8 | ) : MutableSet, ThreadSafeMutableCollection(syncObject, delegate) { 9 | public actual constructor() : this(delegate = mutableSetOf()) 10 | public constructor(delegate: MutableSet) : this(SynchronizedObject(), delegate) 11 | } 12 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/lifecycle/ConfigurationChecker.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | import androidx.compose.runtime.remember 6 | 7 | private val configurationChecker = ConfigurationChecker() 8 | 9 | @Composable 10 | internal actual fun getConfigurationChecker(): ConfigurationChecker { 11 | return remember { configurationChecker } 12 | } 13 | 14 | @Stable 15 | internal actual class ConfigurationChecker { 16 | actual fun isChangingConfigurations(): Boolean = false 17 | } 18 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/lifecycle/Serializable.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.lifecycle 2 | 3 | public actual interface JavaSerializable 4 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/platform/KClassEx.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.platform 2 | 3 | import kotlin.reflect.KClass 4 | 5 | public actual val KClass<*>.multiplatformName: String? get() = qualifiedName 6 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/screen/Screen.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | public actual interface Screen { 6 | public actual val key: ScreenKey 7 | get() = commonKeyGeneration() 8 | 9 | @Composable 10 | public actual fun Content() 11 | } 12 | -------------------------------------------------------------------------------- /voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/screen/ScreenKey.native.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.screen 2 | 3 | import platform.Foundation.NSUUID 4 | 5 | internal actual fun randomUuid(): String = NSUUID().UUIDString() 6 | -------------------------------------------------------------------------------- /voyager-hilt/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-hilt/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | kotlin("kapt") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForAndroidxCompose() 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.hilt" 12 | defaultConfig { 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | kapt { 18 | correctErrorTypes = true 19 | } 20 | 21 | dependencies { 22 | api(projects.voyagerScreenmodel) 23 | api(projects.voyagerNavigator) 24 | 25 | implementation(libs.compose.runtime) 26 | implementation(libs.compose.ui) 27 | implementation(libs.lifecycle.savedState) 28 | implementation(libs.lifecycle.viewModelKtx) 29 | implementation(libs.hilt.android) 30 | implementation(libs.lifecycle.viewModelCompose) 31 | kapt(libs.hilt.compiler) 32 | 33 | testRuntimeOnly(libs.junit.engine) 34 | testImplementation(libs.junit.api) 35 | } 36 | -------------------------------------------------------------------------------- /voyager-hilt/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-hilt/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-hilt/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerHilt 2 | POM_ARTIFACT_ID=voyager-hilt -------------------------------------------------------------------------------- /voyager-hilt/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/OptionalMultibindingsModule.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ActivityRetainedComponent 7 | import dagger.multibindings.Multibinds 8 | 9 | /** 10 | * By default Dagger Multibindings are required to have at least one definition. 11 | * This module says to Dagger that a map with [ScreenModel] or [ScreenModelFactory] can be empty. 12 | */ 13 | @Module 14 | @InstallIn(ActivityRetainedComponent::class) 15 | public abstract class OptionalMultibindingsModule { 16 | @Multibinds 17 | public abstract fun screenModelFactories(): Map, ScreenModelFactory> 18 | 19 | @Multibinds 20 | public abstract fun screenModels(): Map, ScreenModel> 21 | } 22 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import dagger.hilt.EntryPoint 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ActivityComponent 7 | import javax.inject.Provider 8 | 9 | /** 10 | * A Hilt entry point that provide all [ScreenModelFactory] and [ScreenModel] in the graph. 11 | * To have all [ScreenModelFactory] and [ScreenModel] in the graph they must be declared using Multibinding 12 | */ 13 | @EntryPoint 14 | @InstallIn(ActivityComponent::class) 15 | public interface ScreenModelEntryPoint { 16 | 17 | /** 18 | * Provide all custom factories declared using multibinding 19 | */ 20 | public fun screenModelFactories(): 21 | Map, @JvmSuppressWildcards Provider> 22 | 23 | /** 24 | * Provide all screen models declared using multibinding 25 | */ 26 | public fun screenModels(): Map, @JvmSuppressWildcards Provider> 27 | } 28 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelFactory.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | /** 4 | * A custom factory used to instantiate objects with custom constructor. 5 | * Frequently used with Assisted Injection 6 | */ 7 | public interface ScreenModelFactory 8 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelFactoryKey.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | import dagger.MapKey 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * A Dagger multibinding key used to identify a [ScreenModelFactory] 8 | */ 9 | @MustBeDocumented 10 | @Target( 11 | AnnotationTarget.FUNCTION, 12 | AnnotationTarget.PROPERTY_GETTER, 13 | AnnotationTarget.PROPERTY_SETTER 14 | ) 15 | @Retention(AnnotationRetention.RUNTIME) 16 | @MapKey 17 | public annotation class ScreenModelFactoryKey(val value: KClass) 18 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelKey.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * A Dagger multibinding key used to identify a [ScreenModel] 9 | */ 10 | @MustBeDocumented 11 | @Target( 12 | AnnotationTarget.FUNCTION, 13 | AnnotationTarget.PROPERTY_GETTER, 14 | AnnotationTarget.PROPERTY_SETTER 15 | ) 16 | @Retention(AnnotationRetention.RUNTIME) 17 | @MapKey 18 | public annotation class ScreenModelKey(val value: KClass) 19 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/VoyagerHiltViewModelFactories.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.lifecycle.ViewModelProvider 5 | import dagger.hilt.EntryPoint 6 | import dagger.hilt.EntryPoints 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ActivityComponent 9 | import dagger.hilt.android.internal.builders.ViewModelComponentBuilder 10 | import dagger.hilt.android.internal.lifecycle.HiltViewModelFactory 11 | import dagger.hilt.android.internal.lifecycle.HiltViewModelMap 12 | import javax.inject.Inject 13 | 14 | public object VoyagerHiltViewModelFactories { 15 | 16 | public fun getVoyagerFactory( 17 | activity: ComponentActivity, 18 | delegateFactory: ViewModelProvider.Factory 19 | ): ViewModelProvider.Factory { 20 | return EntryPoints.get(activity, ViewModelFactoryEntryPoint::class.java) 21 | .internalViewModelFactory() 22 | .fromActivity(delegateFactory) 23 | } 24 | 25 | internal class InternalViewModelFactory @Inject internal constructor( 26 | @HiltViewModelMap.KeySet private val keySet: Map, Boolean>, 27 | private val viewModelComponentBuilder: ViewModelComponentBuilder 28 | ) { 29 | fun fromActivity( 30 | delegateFactory: ViewModelProvider.Factory 31 | ): ViewModelProvider.Factory { 32 | return HiltViewModelFactory(keySet, delegateFactory, viewModelComponentBuilder) 33 | } 34 | } 35 | 36 | @EntryPoint 37 | @InstallIn(ActivityComponent::class) 38 | internal interface ViewModelFactoryEntryPoint { 39 | fun internalViewModelFactory(): InternalViewModelFactory 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/internal/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.hilt.internal 2 | 3 | import android.content.Context 4 | import android.content.ContextWrapper 5 | import androidx.activity.ComponentActivity 6 | 7 | private inline fun findOwner(context: Context): T? { 8 | var innerContext = context 9 | while (innerContext is ContextWrapper) { 10 | if (innerContext is T) { 11 | return innerContext 12 | } 13 | innerContext = innerContext.baseContext 14 | } 15 | return null 16 | } 17 | 18 | @PublishedApi 19 | internal val Context.componentActivity: ComponentActivity 20 | get() = findOwner(this) 21 | ?: error("Context must be a androidx.activity.ComponentActivity. Current is $this") 22 | -------------------------------------------------------------------------------- /voyager-kodein/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-kodein/api/android/voyager-kodein.api: -------------------------------------------------------------------------------- 1 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScopeKt { 2 | } 3 | 4 | -------------------------------------------------------------------------------- /voyager-kodein/api/desktop/voyager-kodein.api: -------------------------------------------------------------------------------- 1 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScopeKt { 2 | } 3 | 4 | -------------------------------------------------------------------------------- /voyager-kodein/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.kodein" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerScreenmodel) 19 | api(projects.voyagerNavigator) 20 | compileOnly(compose.runtime) 21 | compileOnly(compose.runtimeSaveable) 22 | compileOnly(libs.kodein) 23 | } 24 | 25 | jvmTest.dependencies { 26 | implementation(libs.junit.api) 27 | runtimeOnly(libs.junit.engine) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /voyager-kodein/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-kodein/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-kodein/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerKodein 2 | POM_ARTIFACT_ID=voyager-kodein -------------------------------------------------------------------------------- /voyager-kodein/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.kodein 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.model.ScreenModel 5 | import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel 6 | import cafe.adriel.voyager.core.model.rememberScreenModel 7 | import cafe.adriel.voyager.core.screen.Screen 8 | import cafe.adriel.voyager.navigator.Navigator 9 | import org.kodein.di.compose.localDI 10 | import org.kodein.di.direct 11 | import org.kodein.di.provider 12 | 13 | @Composable 14 | public inline fun Screen.rememberScreenModel( 15 | tag: Any? = null 16 | ): T = with(localDI()) { 17 | rememberScreenModel(tag = tag?.toString()) { direct.provider(tag)() } 18 | } 19 | 20 | @Composable 21 | public inline fun Screen.rememberScreenModel( 22 | tag: Any? = null, 23 | arg: A 24 | ): T = with(localDI()) { 25 | rememberScreenModel(tag = tag?.toString()) { direct.provider(tag, arg)() } 26 | } 27 | 28 | @Composable 29 | public inline fun Navigator.rememberNavigatorScreenModel( 30 | tag: Any? = null 31 | ): T = with(localDI()) { 32 | rememberNavigatorScreenModel(tag = tag?.toString()) { direct.provider(tag)() } 33 | } 34 | 35 | @Composable 36 | public inline fun Navigator.rememberNavigatorScreenModel( 37 | tag: Any? = null, 38 | arg: A 39 | ): T = with(localDI()) { 40 | rememberNavigatorScreenModel(tag = tag?.toString()) { direct.provider(tag, arg)() } 41 | } 42 | -------------------------------------------------------------------------------- /voyager-koin/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-koin/api/android/voyager-koin.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-koin/api/android/voyager-koin.api -------------------------------------------------------------------------------- /voyager-koin/api/desktop/voyager-koin.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-koin/api/desktop/voyager-koin.api -------------------------------------------------------------------------------- /voyager-koin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform( 9 | fullyMultiplatform = true 10 | ) 11 | 12 | android { 13 | namespace = "cafe.adriel.voyager.koin" 14 | defaultConfig { 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | } 18 | 19 | kotlin { 20 | sourceSets { 21 | commonMain.dependencies { 22 | api(projects.voyagerCore) 23 | api(projects.voyagerScreenmodel) 24 | api(projects.voyagerNavigator) 25 | 26 | compileOnly(compose.runtime) 27 | compileOnly(compose.runtimeSaveable) 28 | 29 | implementation(libs.coroutines.core) 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | commonMainImplementation(libs.koin.compose) { 36 | exclude("org.jetbrains.compose.runtime") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /voyager-koin/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-koin/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-koin/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerKoin 2 | POM_ARTIFACT_ID=voyager-koin -------------------------------------------------------------------------------- /voyager-koin/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/api/android/voyager-lifecycle-kmp.api: -------------------------------------------------------------------------------- 1 | public final class cafe/adriel/voyager/jetpack/LifecycleProviderKt { 2 | } 3 | 4 | public final class cafe/adriel/voyager/jetpack/NavigatorKt { 5 | } 6 | 7 | public final class cafe/adriel/voyager/jetpack/NavigatorLifecycleKMPOwnerKt { 8 | } 9 | 10 | public final class cafe/adriel/voyager/jetpack/VoyagerLifecycleKMPOwner$Companion { 11 | } 12 | 13 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/api/desktop/voyager-lifecycle-kmp.api: -------------------------------------------------------------------------------- 1 | public final class cafe/adriel/voyager/jetpack/LifecycleProviderKt { 2 | } 3 | 4 | public final class cafe/adriel/voyager/jetpack/NavigatorKt { 5 | } 6 | 7 | public final class cafe/adriel/voyager/jetpack/NavigatorLifecycleKMPOwnerKt { 8 | } 9 | 10 | public final class cafe/adriel/voyager/jetpack/VoyagerLifecycleKMPOwner$Companion { 11 | } 12 | 13 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.lifecycle.kmp" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerNavigator) 19 | compileOnly(compose.runtime) 20 | compileOnly(compose.runtimeSaveable) 21 | implementation(libs.androidxKmp.lifecycle.viewmodelCompose) 22 | implementation(libs.androidxKmp.lifecycle.viewmodel) 23 | implementation(libs.androidxKmp.lifecycle.runtimeCompose) 24 | implementation(libs.androidxKmp.core.bundle) 25 | } 26 | 27 | jvmTest.dependencies { 28 | implementation(libs.junit.api) 29 | runtimeOnly(libs.junit.engine) 30 | } 31 | 32 | androidMain.dependencies { 33 | compileOnly(libs.lifecycle.savedState) 34 | compileOnly(compose.ui) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerLifecycleKMP 2 | POM_ARTIFACT_ID=voyager-lifecycle-kmp -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/src/androidMain/kotlin/cafe/adriel/voyager/jetpack/AndroidScreenLifecycleOwner.android.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.jetpack 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.ProvidedValue 7 | import androidx.compose.ui.platform.LocalContext 8 | import androidx.compose.ui.platform.LocalSavedStateRegistryOwner 9 | import androidx.lifecycle.SavedStateViewModelFactory 10 | import androidx.lifecycle.ViewModelProvider 11 | import androidx.lifecycle.viewmodel.MutableCreationExtras 12 | import androidx.savedstate.SavedStateRegistryOwner 13 | import java.util.concurrent.atomic.AtomicReference 14 | 15 | internal actual class SavedStateViewModelPlatform actual constructor(val owner: SavedStateRegistryOwner) { 16 | private val atomicAppContext = AtomicReference() 17 | 18 | actual fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory = 19 | SavedStateViewModelFactory( 20 | application = atomicAppContext.get()?.getApplication(), 21 | owner = owner 22 | ) 23 | 24 | actual fun providePlatform(extras: MutableCreationExtras) { 25 | val application = atomicAppContext.get()?.getApplication() 26 | if (application != null) { 27 | extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, application) 28 | } 29 | } 30 | 31 | @Composable 32 | actual fun initHooks() { 33 | atomicAppContext.compareAndSet(null, LocalContext.current.applicationContext) 34 | } 35 | 36 | actual fun provideHooks(): List> = listOf( 37 | LocalSavedStateRegistryOwner provides owner, 38 | androidx.lifecycle.compose.LocalLifecycleOwner provides owner 39 | ) 40 | 41 | private fun Context.getApplication(): Application? = when (this) { 42 | is Application -> this 43 | else -> null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/LifecycleProvider.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.jetpack 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import androidx.compose.runtime.remember 6 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi 7 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 8 | import cafe.adriel.voyager.core.lifecycle.LocalNavigatorScreenLifecycleProvider 9 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider 10 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider 11 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore 12 | import cafe.adriel.voyager.core.screen.Screen 13 | 14 | @ExperimentalVoyagerApi 15 | @Composable 16 | public fun ProvideNavigatorLifecycleKMPSupport( 17 | content: @Composable () -> Unit 18 | ) { 19 | val provider = remember { JetpackSupportProvider() } 20 | CompositionLocalProvider( 21 | LocalNavigatorScreenLifecycleProvider provides provider 22 | ) { 23 | content() 24 | } 25 | } 26 | 27 | @InternalVoyagerApi 28 | @ExperimentalVoyagerApi 29 | public class JetpackSupportProvider : NavigatorScreenLifecycleProvider { 30 | override fun provide(screen: Screen): List = 31 | listOf(ScreenLifecycleStore.get(screen) { ScreenLifecycleKMPOwner() }) 32 | } 33 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/NavigatorLifecycleKMPOwner.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.jetpack 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi 6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 7 | import cafe.adriel.voyager.navigator.Navigator 8 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorDisposable 9 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore 10 | 11 | @InternalVoyagerApi 12 | @ExperimentalVoyagerApi 13 | @Composable 14 | public fun Navigator.rememberNavigatorLifecycleOwner(): VoyagerLifecycleKMPOwner { 15 | return remember(this) { 16 | NavigatorLifecycleStore.get(this) { 17 | NavigatorLifecycleKMPOwner() 18 | }.owner 19 | } 20 | } 21 | 22 | @InternalVoyagerApi 23 | @ExperimentalVoyagerApi 24 | public class NavigatorLifecycleKMPOwner : NavigatorDisposable { 25 | public val owner: VoyagerLifecycleKMPOwner = VoyagerLifecycleKMPOwner() 26 | 27 | override fun onDispose(navigator: Navigator) { 28 | owner.onDispose() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/ScreenLifecycleJetpackOwner.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.jetpack 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi 6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner 8 | import cafe.adriel.voyager.core.screen.Screen 9 | 10 | @InternalVoyagerApi 11 | @ExperimentalVoyagerApi 12 | public class ScreenLifecycleKMPOwner : ScreenLifecycleOwner { 13 | public val owner: VoyagerLifecycleKMPOwner = VoyagerLifecycleKMPOwner() 14 | 15 | @Composable 16 | override fun ProvideBeforeScreenContent( 17 | provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit, 18 | content: @Composable () -> Unit 19 | ) { 20 | provideSaveableState("lifecycle") { 21 | owner.LifecycleDisposableEffect() 22 | 23 | val hooks = owner.getHooks() 24 | 25 | CompositionLocalProvider(*hooks.toTypedArray()) { 26 | content() 27 | } 28 | } 29 | } 30 | 31 | override fun onDispose(screen: Screen) { 32 | owner.onDispose() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /voyager-lifecycle-kmp/src/nonAndroidMain/kotlin/cafe/adriel/voyager/jetpack/AndroidScreenLifecycleOwner.nonAndroid.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.jetpack 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ProvidedValue 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.viewmodel.MutableCreationExtras 7 | import androidx.savedstate.SavedStateRegistryOwner 8 | 9 | internal actual class SavedStateViewModelPlatform actual constructor( 10 | owner: SavedStateRegistryOwner 11 | ) { 12 | public actual fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory { 13 | return object : ViewModelProvider.Factory { 14 | } 15 | } 16 | 17 | public actual fun providePlatform(extras: MutableCreationExtras) {} 18 | 19 | @Composable 20 | public actual fun initHooks() {} 21 | 22 | public actual fun provideHooks(): List> = emptyList() 23 | } 24 | -------------------------------------------------------------------------------- /voyager-livedata/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-livedata/api/voyager-livedata.api: -------------------------------------------------------------------------------- 1 | public abstract class cafe/adriel/voyager/livedata/LiveScreenModel : cafe/adriel/voyager/core/model/ScreenModel { 2 | public static final field $stable I 3 | public fun (Ljava/lang/Object;)V 4 | protected final fun getMutableState ()Landroidx/lifecycle/MutableLiveData; 5 | public final fun getState ()Landroidx/lifecycle/LiveData; 6 | public fun onDispose ()V 7 | } 8 | 9 | -------------------------------------------------------------------------------- /voyager-livedata/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("android") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | setupModuleForAndroidxCompose() 8 | 9 | android { 10 | namespace = "cafe.adriel.voyager.livedata" 11 | defaultConfig { 12 | consumerProguardFiles("consumer-rules.pro") 13 | } 14 | } 15 | 16 | dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerScreenmodel) 19 | 20 | implementation(libs.compose.runtimeLiveData) 21 | 22 | testRuntimeOnly(libs.junit.engine) 23 | testImplementation(libs.junit.api) 24 | } 25 | -------------------------------------------------------------------------------- /voyager-livedata/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-livedata/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-livedata/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerLiveData 2 | POM_ARTIFACT_ID=voyager-livedata -------------------------------------------------------------------------------- /voyager-livedata/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-livedata/src/main/java/cafe/adriel/voyager/livedata/LiveScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.livedata 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import cafe.adriel.voyager.core.model.ScreenModel 6 | 7 | public abstract class LiveScreenModel(initialState: S) : ScreenModel { 8 | 9 | protected val mutableState: MutableLiveData = MutableLiveData(initialState) 10 | public val state: LiveData = mutableState 11 | } 12 | -------------------------------------------------------------------------------- /voyager-navigator/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-navigator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.navigator" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | compileOnly(compose.runtime) 19 | compileOnly(compose.runtimeSaveable) 20 | } 21 | 22 | jvmTest.dependencies { 23 | implementation(libs.junit.api) 24 | runtimeOnly(libs.junit.engine) 25 | } 26 | 27 | androidMain.dependencies { 28 | implementation(libs.compose.activity) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /voyager-navigator/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-navigator/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-navigator/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerNavigator 2 | POM_ARTIFACT_ID=voyager-navigator -------------------------------------------------------------------------------- /voyager-navigator/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator 2 | 3 | import android.os.Parcelable 4 | import androidx.compose.runtime.saveable.listSaver 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | /** 8 | * Navigator Saver that forces all Screens be [Parcelable], if not, it will throw a exception while trying to save 9 | * the navigator state. 10 | */ 11 | public fun parcelableNavigatorSaver(): NavigatorSaver = 12 | NavigatorSaver { _, key, stateHolder, disposeBehavior, parent -> 13 | listSaver( 14 | save = { navigator -> 15 | val screenAsParcelables = navigator.items.filterIsInstance() 16 | 17 | if (navigator.items.size > screenAsParcelables.size) { 18 | val screensNotParcelable = 19 | navigator.items.filterNot { screen -> screenAsParcelables.any { screen == it } } 20 | .map { it::class.simpleName } 21 | .joinToString() 22 | 23 | throw VoyagerNavigatorSaverException( 24 | "Unable to save instance state for Screens: $screensNotParcelable. " + 25 | "Implement android.os.Parcelable on your Screen." 26 | ) 27 | } 28 | 29 | screenAsParcelables 30 | }, 31 | restore = { items -> Navigator(items as List, key, stateHolder, disposeBehavior, parent) } 32 | ) 33 | } 34 | 35 | public class VoyagerNavigatorSaverException(message: String) : RuntimeException(message) 36 | -------------------------------------------------------------------------------- /voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.activity.compose.BackHandler 4 | import androidx.compose.runtime.Composable 5 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 6 | 7 | @InternalVoyagerApi 8 | @Composable 9 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = BackHandler(enabled, onBack) 10 | -------------------------------------------------------------------------------- /voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.android.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import cafe.adriel.voyager.androidx.AndroidScreenLifecycleOwner 4 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider 5 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider 6 | import cafe.adriel.voyager.core.screen.Screen 7 | 8 | internal actual class DefaultNavigatorScreenLifecycleProvider actual constructor() : NavigatorScreenLifecycleProvider { 9 | actual override fun provide(screen: Screen): List = 10 | listOf(AndroidScreenLifecycleOwner.get(screen)) 11 | } 12 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import cafe.adriel.voyager.core.lifecycle.LocalNavigatorScreenLifecycleProvider 6 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider 7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider 8 | import cafe.adriel.voyager.core.screen.Screen 9 | 10 | internal val defaultNavigatorScreenLifecycleProvider = DefaultNavigatorScreenLifecycleProvider() 11 | 12 | internal expect class DefaultNavigatorScreenLifecycleProvider() : NavigatorScreenLifecycleProvider { 13 | override fun provide(screen: Screen): List 14 | } 15 | 16 | @Composable 17 | internal fun getNavigatorScreenLifecycleProvider(screen: Screen): List { 18 | val navigatorScreenLifecycleProvider = LocalNavigatorScreenLifecycleProvider.current 19 | ?: defaultNavigatorScreenLifecycleProvider 20 | 21 | return remember(screen.key) { 22 | navigatorScreenLifecycleProvider.provide(screen) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorBackHandler.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | import cafe.adriel.voyager.navigator.Navigator 6 | import cafe.adriel.voyager.navigator.OnBackPressed 7 | 8 | @InternalVoyagerApi 9 | @Composable 10 | public expect fun BackHandler(enabled: Boolean, onBack: () -> Unit) 11 | 12 | @Composable 13 | internal fun NavigatorBackHandler( 14 | navigator: Navigator, 15 | onBackPressed: OnBackPressed 16 | ) { 17 | if (onBackPressed != null) { 18 | BackHandler( 19 | enabled = navigator.canPop || navigator.parent?.canPop ?: false, 20 | onBack = { 21 | if (onBackPressed(navigator.lastItem)) { 22 | if (navigator.pop().not()) { 23 | navigator.parent?.pop() 24 | } 25 | } 26 | } 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ProvidableCompositionLocal 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.runtime.saveable.SaveableStateHolder 7 | import androidx.compose.runtime.saveable.rememberSaveable 8 | import androidx.compose.runtime.staticCompositionLocalOf 9 | import cafe.adriel.voyager.core.screen.Screen 10 | import cafe.adriel.voyager.navigator.LocalNavigatorSaver 11 | import cafe.adriel.voyager.navigator.Navigator 12 | import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior 13 | 14 | internal val LocalNavigatorStateHolder: ProvidableCompositionLocal = 15 | staticCompositionLocalOf { error("LocalNavigatorStateHolder not initialized") } 16 | 17 | @Composable 18 | internal fun rememberNavigator( 19 | screens: List, 20 | key: String, 21 | disposeBehavior: NavigatorDisposeBehavior, 22 | parent: Navigator? 23 | ): Navigator { 24 | val stateHolder = LocalNavigatorStateHolder.current 25 | val navigatorSaver = LocalNavigatorSaver.current 26 | val saver = remember(navigatorSaver, stateHolder, parent, disposeBehavior) { 27 | navigatorSaver.saver(screens, key, stateHolder, disposeBehavior, parent) 28 | } 29 | 30 | return rememberSaveable(saver = saver, key = key) { 31 | Navigator(screens, key, stateHolder, disposeBehavior, parent) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/lifecycle/NavigatorDisposable.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.lifecycle 2 | 3 | import cafe.adriel.voyager.navigator.Navigator 4 | 5 | public interface NavigatorDisposable { 6 | public fun onDispose(navigator: Navigator) 7 | } 8 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/lifecycle/NavigatorLifecycleStore.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.lifecycle 2 | 3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap 4 | import cafe.adriel.voyager.navigator.Navigator 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.typeOf 7 | 8 | public typealias NavigatorKey = String 9 | 10 | public object NavigatorLifecycleStore { 11 | 12 | private val owners = ThreadSafeMap>() 13 | 14 | /** 15 | * Register a NavigatorDisposable that will be called `onDispose` on the 16 | * [navigator] leaves the Composition. 17 | */ 18 | public inline fun get( 19 | navigator: Navigator, 20 | noinline factory: (NavigatorKey) -> T 21 | ): T { 22 | return get(navigator, typeOf(), factory) as T 23 | } 24 | 25 | @PublishedApi 26 | internal fun get( 27 | navigator: Navigator, 28 | screenDisposeListenerType: KType, 29 | factory: (NavigatorKey) -> T 30 | ): NavigatorDisposable { 31 | return owners.getOrPut(navigator.key) { 32 | ThreadSafeMap().apply { 33 | put(screenDisposeListenerType, factory(navigator.key)) 34 | } 35 | }.getOrPut(screenDisposeListenerType) { 36 | factory(navigator.key) 37 | } 38 | } 39 | 40 | public fun remove(navigator: Navigator) { 41 | owners.remove(navigator.key)?.forEach { it.value.onDispose(navigator) } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /voyager-navigator/src/commonWebMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.web.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | 6 | @InternalVoyagerApi 7 | @Composable 8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit 9 | -------------------------------------------------------------------------------- /voyager-navigator/src/desktopMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | 6 | @InternalVoyagerApi 7 | @Composable 8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit 9 | -------------------------------------------------------------------------------- /voyager-navigator/src/iosMain/kotlin/cafe.adriel.voyager.navigator.internal/Actuals.uikit.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | 6 | @InternalVoyagerApi 7 | @Composable 8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit 9 | -------------------------------------------------------------------------------- /voyager-navigator/src/macosMain/kotlin/cafe.adriel.voyager.navigator.internal/Actuals.macos.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 5 | 6 | @InternalVoyagerApi 7 | @Composable 8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit 9 | -------------------------------------------------------------------------------- /voyager-navigator/src/nonAndroidMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.nonAndroid.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.internal 2 | 3 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider 4 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | internal actual class DefaultNavigatorScreenLifecycleProvider actual constructor() : NavigatorScreenLifecycleProvider { 8 | actual override fun provide(screen: Screen): List = 9 | emptyList() 10 | } 11 | -------------------------------------------------------------------------------- /voyager-rxjava/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-rxjava/api/android/voyager-rxjava.api: -------------------------------------------------------------------------------- 1 | public abstract class cafe/adriel/voyager/rxjava/RxScreenModel : cafe/adriel/voyager/core/model/ScreenModel { 2 | public fun ()V 3 | protected final fun getMutableState ()Lio/reactivex/rxjava3/subjects/BehaviorSubject; 4 | public final fun getState ()Lio/reactivex/rxjava3/core/Observable; 5 | public fun onDispose ()V 6 | } 7 | 8 | public final class cafe/adriel/voyager/rxjava/ScreenModelKt { 9 | public static final fun getDisposables (Lcafe/adriel/voyager/core/model/ScreenModel;)Lio/reactivex/rxjava3/disposables/CompositeDisposable; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /voyager-rxjava/api/desktop/voyager-rxjava.api: -------------------------------------------------------------------------------- 1 | public abstract class cafe/adriel/voyager/rxjava/RxScreenModel : cafe/adriel/voyager/core/model/ScreenModel { 2 | public fun ()V 3 | protected final fun getMutableState ()Lio/reactivex/rxjava3/subjects/BehaviorSubject; 4 | public final fun getState ()Lio/reactivex/rxjava3/core/Observable; 5 | public fun onDispose ()V 6 | } 7 | 8 | public final class cafe/adriel/voyager/rxjava/ScreenModelKt { 9 | public static final fun getDisposables (Lcafe/adriel/voyager/core/model/ScreenModel;)Lio/reactivex/rxjava3/disposables/CompositeDisposable; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /voyager-rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | setupModuleForComposeMultiplatform() 8 | 9 | android { 10 | namespace = "cafe.adriel.voyager.rxjava" 11 | } 12 | 13 | kotlin { 14 | sourceSets { 15 | jvmMain.dependencies { 16 | api(projects.voyagerCore) 17 | api(projects.voyagerScreenmodel) 18 | compileOnly(libs.rxjava) 19 | } 20 | jvmTest.dependencies { 21 | implementation(libs.junit.api) 22 | runtimeOnly(libs.junit.engine) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /voyager-rxjava/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-rxjava/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-rxjava/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerRxJava 2 | POM_ARTIFACT_ID=voyager-rxjava -------------------------------------------------------------------------------- /voyager-rxjava/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-rxjava/src/jvmMain/kotlin/cafe/adriel/voyager/rxjava/ScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.rxjava 2 | 3 | import cafe.adriel.voyager.core.model.ScreenModel 4 | import cafe.adriel.voyager.core.model.ScreenModelStore 5 | import io.reactivex.rxjava3.core.Observable 6 | import io.reactivex.rxjava3.disposables.CompositeDisposable 7 | import io.reactivex.rxjava3.subjects.BehaviorSubject 8 | 9 | public val ScreenModel.disposables: CompositeDisposable 10 | get() = ScreenModelStore.getOrPutDependency( 11 | screenModel = this, 12 | name = "ScreenModelCompositeDisposable", 13 | factory = { CompositeDisposable() }, 14 | onDispose = { disposables -> disposables.clear() } 15 | ) 16 | 17 | public abstract class RxScreenModel : ScreenModel { 18 | 19 | protected val mutableState: BehaviorSubject = BehaviorSubject.create() 20 | public val state: Observable = mutableState 21 | } 22 | -------------------------------------------------------------------------------- /voyager-screenmodel/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-screenmodel/api/android/voyager-screenmodel.api: -------------------------------------------------------------------------------- 1 | public abstract interface class cafe/adriel/voyager/core/model/ScreenModel { 2 | public abstract fun onDispose ()V 3 | } 4 | 5 | public final class cafe/adriel/voyager/core/model/ScreenModel$DefaultImpls { 6 | public static fun onDispose (Lcafe/adriel/voyager/core/model/ScreenModel;)V 7 | } 8 | 9 | public final class cafe/adriel/voyager/core/model/ScreenModelKt { 10 | public static final fun getScreenModelScope (Lcafe/adriel/voyager/core/model/ScreenModel;)Lkotlinx/coroutines/CoroutineScope; 11 | } 12 | 13 | public final class cafe/adriel/voyager/core/model/ScreenModelStore : cafe/adriel/voyager/core/lifecycle/ScreenDisposable { 14 | public static final field $stable I 15 | public static final field INSTANCE Lcafe/adriel/voyager/core/model/ScreenModelStore; 16 | public final fun getDependencies ()Ljava/util/Map; 17 | public final fun getDependencyKey (Lcafe/adriel/voyager/core/model/ScreenModel;Ljava/lang/String;)Ljava/lang/String; 18 | public final fun getLastScreenModelKey ()Lkotlinx/coroutines/flow/MutableStateFlow; 19 | public final fun getScreenModels ()Ljava/util/Map; 20 | public fun onDispose (Lcafe/adriel/voyager/core/screen/Screen;)V 21 | } 22 | 23 | public abstract class cafe/adriel/voyager/core/model/StateScreenModel : cafe/adriel/voyager/core/model/ScreenModel { 24 | public static final field $stable I 25 | public fun (Ljava/lang/Object;)V 26 | protected final fun getMutableState ()Lkotlinx/coroutines/flow/MutableStateFlow; 27 | public final fun getState ()Lkotlinx/coroutines/flow/StateFlow; 28 | public fun onDispose ()V 29 | } 30 | 31 | -------------------------------------------------------------------------------- /voyager-screenmodel/api/desktop/voyager-screenmodel.api: -------------------------------------------------------------------------------- 1 | public abstract interface class cafe/adriel/voyager/core/model/ScreenModel { 2 | public abstract fun onDispose ()V 3 | } 4 | 5 | public final class cafe/adriel/voyager/core/model/ScreenModel$DefaultImpls { 6 | public static fun onDispose (Lcafe/adriel/voyager/core/model/ScreenModel;)V 7 | } 8 | 9 | public final class cafe/adriel/voyager/core/model/ScreenModelKt { 10 | public static final fun getScreenModelScope (Lcafe/adriel/voyager/core/model/ScreenModel;)Lkotlinx/coroutines/CoroutineScope; 11 | } 12 | 13 | public final class cafe/adriel/voyager/core/model/ScreenModelStore : cafe/adriel/voyager/core/lifecycle/ScreenDisposable { 14 | public static final field $stable I 15 | public static final field INSTANCE Lcafe/adriel/voyager/core/model/ScreenModelStore; 16 | public final fun getDependencies ()Ljava/util/Map; 17 | public final fun getDependencyKey (Lcafe/adriel/voyager/core/model/ScreenModel;Ljava/lang/String;)Ljava/lang/String; 18 | public final fun getLastScreenModelKey ()Lkotlinx/coroutines/flow/MutableStateFlow; 19 | public final fun getScreenModels ()Ljava/util/Map; 20 | public fun onDispose (Lcafe/adriel/voyager/core/screen/Screen;)V 21 | } 22 | 23 | public abstract class cafe/adriel/voyager/core/model/StateScreenModel : cafe/adriel/voyager/core/model/ScreenModel { 24 | public static final field $stable I 25 | public fun (Ljava/lang/Object;)V 26 | protected final fun getMutableState ()Lkotlinx/coroutines/flow/MutableStateFlow; 27 | public final fun getState ()Lkotlinx/coroutines/flow/StateFlow; 28 | public fun onDispose ()V 29 | } 30 | 31 | -------------------------------------------------------------------------------- /voyager-screenmodel/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.screenmodel" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerNavigator) 19 | compileOnly(compose.runtime) 20 | compileOnly(compose.runtimeSaveable) 21 | implementation(libs.coroutines.core) 22 | } 23 | 24 | jvmTest.dependencies { 25 | implementation(libs.junit.api) 26 | runtimeOnly(libs.junit.engine) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /voyager-screenmodel/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-screenmodel/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-screenmodel/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerScreenModel 2 | POM_ARTIFACT_ID=voyager-screenmodel -------------------------------------------------------------------------------- /voyager-screenmodel/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-screenmodel/src/commonMain/kotlin/cafe/adriel/voyager/core/model/NavigatorScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisallowComposableCalls 5 | import androidx.compose.runtime.remember 6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi 7 | import cafe.adriel.voyager.navigator.Navigator 8 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorDisposable 9 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore 10 | 11 | @Composable 12 | public inline fun Navigator.rememberNavigatorScreenModel( 13 | tag: String? = null, 14 | crossinline factory: @DisallowComposableCalls () -> T 15 | ): T { 16 | // register the navigator lifecycle listener if is not already registered 17 | remember(this) { 18 | NavigatorLifecycleStore.get(this) { NavigatorScreenModelDisposer } 19 | } 20 | 21 | return remember(ScreenModelStore.getKey(this.key, tag)) { 22 | ScreenModelStore.getOrPut(this.key, tag, factory) 23 | } 24 | } 25 | 26 | @InternalVoyagerApi 27 | public object NavigatorScreenModelDisposer : NavigatorDisposable { 28 | override fun onDispose(navigator: Navigator) { 29 | ScreenModelStore.onDisposeNavigator(navigator.key) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /voyager-screenmodel/src/commonMain/kotlin/cafe/adriel/voyager/core/model/ScreenModel.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.core.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisallowComposableCalls 5 | import androidx.compose.runtime.remember 6 | import cafe.adriel.voyager.core.concurrent.PlatformMainDispatcher 7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import kotlinx.coroutines.CoroutineName 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.SupervisorJob 12 | import kotlinx.coroutines.cancel 13 | import kotlinx.coroutines.flow.MutableStateFlow 14 | import kotlinx.coroutines.flow.StateFlow 15 | import kotlinx.coroutines.flow.asStateFlow 16 | 17 | public val ScreenModel.screenModelScope: CoroutineScope 18 | get() = ScreenModelStore.getOrPutDependency( 19 | screenModel = this, 20 | name = "ScreenModelCoroutineScope", 21 | factory = { key -> CoroutineScope(SupervisorJob() + PlatformMainDispatcher + CoroutineName(key)) }, 22 | onDispose = { scope -> scope.cancel() } 23 | ) 24 | 25 | @Composable 26 | public inline fun Screen.rememberScreenModel( 27 | tag: String? = null, 28 | crossinline factory: @DisallowComposableCalls () -> T 29 | ): T { 30 | val screenModelStore = remember(this) { 31 | ScreenLifecycleStore.get(this) { ScreenModelStore } 32 | } 33 | return remember(screenModelStore.getKey(this, tag)) { 34 | screenModelStore.getOrPut(this, tag, factory) 35 | } 36 | } 37 | 38 | public interface ScreenModel { 39 | 40 | public fun onDispose() {} 41 | } 42 | 43 | public abstract class StateScreenModel(initialState: S) : ScreenModel { 44 | 45 | protected val mutableState: MutableStateFlow = MutableStateFlow(initialState) 46 | public val state: StateFlow = mutableState.asStateFlow() 47 | } 48 | -------------------------------------------------------------------------------- /voyager-tab-navigator/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-tab-navigator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.navigator.tab" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerNavigator) 19 | compileOnly(compose.runtime) 20 | compileOnly(compose.ui) 21 | } 22 | 23 | jvmTest.dependencies { 24 | implementation(libs.junit.api) 25 | runtimeOnly(libs.junit.engine) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /voyager-tab-navigator/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-tab-navigator/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-tab-navigator/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerTabNavigator 2 | POM_ARTIFACT_ID=voyager-tab-navigator -------------------------------------------------------------------------------- /voyager-tab-navigator/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-tab-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/tab/Tab.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.navigator.tab 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.painter.Painter 5 | import cafe.adriel.voyager.core.screen.Screen 6 | 7 | @Composable 8 | public fun CurrentTab() { 9 | val tabNavigator = LocalTabNavigator.current 10 | val currentTab = tabNavigator.current 11 | 12 | tabNavigator.saveableState("currentTab") { 13 | currentTab.Content() 14 | } 15 | } 16 | 17 | public data class TabOptions( 18 | val index: UShort, 19 | val title: String, 20 | val icon: Painter? = null 21 | ) 22 | 23 | public interface Tab : Screen { 24 | 25 | public val options: TabOptions 26 | @Composable get 27 | } 28 | -------------------------------------------------------------------------------- /voyager-transitions/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /voyager-transitions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.android.library") 4 | id("org.jetbrains.compose") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | setupModuleForComposeMultiplatform(fullyMultiplatform = true) 9 | 10 | android { 11 | namespace = "cafe.adriel.voyager.transitions" 12 | } 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.voyagerCore) 18 | api(projects.voyagerNavigator) 19 | compileOnly(compose.animation) 20 | } 21 | 22 | jvmTest.dependencies { 23 | implementation(libs.junit.api) 24 | runtimeOnly(libs.junit.engine) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /voyager-transitions/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrielcafe/voyager/962f5d86569caf74b269ff7cb5d0e97f3a6f79c0/voyager-transitions/consumer-rules.pro -------------------------------------------------------------------------------- /voyager-transitions/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=VoyagerTransitions 2 | POM_ARTIFACT_ID=voyager-transitions -------------------------------------------------------------------------------- /voyager-transitions/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/CrossfadeTransition.kt: -------------------------------------------------------------------------------- 1 | package cafe.adriel.voyager.transitions 2 | 3 | import androidx.compose.animation.Crossfade 4 | import androidx.compose.animation.core.FiniteAnimationSpec 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.navigator.Navigator 10 | 11 | @Composable 12 | public fun CrossfadeTransition( 13 | navigator: Navigator, 14 | animationSpec: FiniteAnimationSpec = tween(), 15 | label: String = "Crossfade", 16 | modifier: Modifier = Modifier, 17 | content: @Composable (Screen) -> Unit = { it.Content() } 18 | ) { 19 | Crossfade( 20 | targetState = navigator.lastItem, 21 | animationSpec = animationSpec, 22 | modifier = modifier, 23 | label = label 24 | ) { screen -> 25 | navigator.saveableState("transition", screen) { 26 | content(screen) 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------