diff --git a/.github/workflows/push_dev.yml b/.github/workflows/push_dev.yml index c0555715..c258bc2a 100644 --- a/.github/workflows/push_dev.yml +++ b/.github/workflows/push_dev.yml @@ -7,15 +7,24 @@ on: push: branches: ['dev'] +env: + GITHUB_ACTOR: '${{ github.actor }}' + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + OSSRH_USERNAME: '${{ secrets.OSSRH_USERNAME }}' + OSSRH_PASSWORD: '${{ secrets.OSSRH_PASSWORD }}' + STAGING_PROFILE_ID: '${{ secrets.STAGING_PROFILE_ID }}' + SIGNING_KEY_ID: '${{ secrets.SIGNING_KEY_ID }}' + SIGNING_KEY: '${{ secrets.SIGNING_KEY }}' + SIGNING_PASSWORD: '${{ secrets.SIGNING_PASSWORD }}' + JB_SIGNING_KEY: '${{ secrets.JB_SIGNING_KEY }}' + JB_CHAIN: '${{ secrets.JB_CHAIN }}' + JB_PASSPHRASE: '${{ secrets.JB_PASSPHRASE }}' + JB_MARKETPLACE_TOKEN: '${{ secrets.JB_MARKETPLACE_TOKEN }}' + jobs: - testOnAll: - strategy: - fail-fast: false - matrix: - java: [17] - os: ['ubuntu-latest', 'windows-latest'] - name: 'Test on ${{ matrix.os }} JDK ${{ matrix.java }}' - runs-on: '${{ matrix.os }}' + ktlintCheck: + name: 'KtlintCheck on macos-latst' + runs-on: 'macos-latest' steps: - uses: 'actions/checkout@v3' with: @@ -25,38 +34,59 @@ jobs: uses: 'actions/setup-java@v2' with: distribution: 'temurin' - java-version: '${{ matrix.java }}' + java-version: '17' - name: 'Run checks with Gradle' - run: './gradlew check --no-daemon --stacktrace' + run: './gradlew ktlintCheck --no-daemon --stacktrace' - publishArtifactsOnMacOs: - runs-on: 'macos-latest' - needs: ['testOnAll'] - name: 'Build and publish snapshots' - env: - GITHUB_ACTOR: '${{ github.actor }}' - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - OSSRH_USERNAME: '${{ secrets.OSSRH_USERNAME }}' - OSSRH_PASSWORD: '${{ secrets.OSSRH_PASSWORD }}' - STAGING_PROFILE_ID: '${{ secrets.STAGING_PROFILE_ID }}' - SIGNING_KEY_ID: '${{ secrets.SIGNING_KEY_ID }}' - SIGNING_KEY: '${{ secrets.SIGNING_KEY }}' - SIGNING_PASSWORD: '${{ secrets.SIGNING_PASSWORD }}' - JB_SIGNING_KEY: '${{ secrets.JB_SIGNING_KEY }}' - JB_CHAIN: '${{ secrets.JB_CHAIN }}' - JB_PASSPHRASE: '${{ secrets.JB_PASSPHRASE }}' - JB_MARKETPLACE_TOKEN: '${{ secrets.JB_MARKETPLACE_TOKEN }}' + unitTest: + needs: ['ktlintCheck'] + strategy: + matrix: + config: + - { target: 'testDebugUnitTest', os: 'ubuntu-latest', java: 17 } + - { target: 'testReleaseUnitTest', os: 'ubuntu-latest', java: 17 } + - { target: 'iosArm64Test', os: 'macos-latest', java: 17 } + - { target: 'iosSimulatorArm64Test', os: 'macos-latest', java: 17 } + - { target: 'iosX64Test', os: 'macos-latest', java: 17 } + - { target: 'jsTest', os: 'ubuntu-latest', java: 17 } + - { target: 'jvmTest', os: 'ubuntu-latest', java: 17 } + - { target: 'wasmJsTest', os: 'ubuntu-latest', java: 17 } + name: 'Run ${{ matrix.config.target }} on ${{ matrix.config.os }} JDK ${{ matrix.config.java }}' + runs-on: '${{ matrix.config.os }}' steps: - uses: 'actions/checkout@v3' with: submodules: 'recursive' fetch-depth: 0 # all commit history and tags - - name: 'Set up JDK 17' - uses: 'actions/setup-java@v2' + - uses: 'actions/setup-java@v2' + with: + distribution: 'temurin' + java-version: ${{ matrix.config.java }} + - run: './gradlew ${{ matrix.config.target }} --stacktrace' + + publishSnapshotArtifacts: + needs: ['unitTest'] + strategy: + matrix: + config: + - { target: 'AndroidDebug', os: 'ubuntu-latest', java: 17 } + - { target: 'AndroidRelease', os: 'ubuntu-latest', java: 17 } + - { target: 'IosArm64', os: 'macos-latest', java: 17 } + - { target: 'IosSimulatorArm64', os: 'macos-latest', java: 17 } + - { target: 'IosX64', os: 'macos-latest', java: 17 } + - { target: 'Js', os: 'ubuntu-latest', java: 17 } + - { target: 'Jvm', os: 'ubuntu-latest', java: 17 } + - { target: 'KotlinMultiplatform', os: 'ubuntu-latest', java: 17 } + - { target: 'WasmJs', os: 'ubuntu-latest', java: 17 } + name: 'Publish ${{ matrix.config.target }} snapshot artifacts on ${{ matrix.config.os }} JDK ${{ matrix.config.java }}' + runs-on: '${{ matrix.config.os }}' + steps: + - uses: 'actions/checkout@v3' + with: + submodules: 'recursive' + fetch-depth: 0 # all commit history and tags + - uses: 'actions/setup-java@v2' with: distribution: 'temurin' - java-version: 17 - - name: 'Assemble Artifacts' - run: './gradlew assemble -x orchidBuild --stacktrace' - - name: 'Publish Artifacts to MavenCentral Snapshots Repository' - run: './gradlew publishAllPublicationsToMavenCentralSnapshotsRepository --stacktrace' + java-version: ${{ matrix.config.java }} + - run: './gradlew publish${{ matrix.config.target }}PublicationToMavenCentralSnapshotsRepository' diff --git a/.github/workflows/push_main.yml b/.github/workflows/push_main.yml index 92c24ffd..c4f01a32 100644 --- a/.github/workflows/push_main.yml +++ b/.github/workflows/push_main.yml @@ -60,30 +60,18 @@ jobs: strategy: matrix: java: [17] - os: ['ubuntu-latest', 'macos-latest'] - target: [ - 'AndroidDebug', - 'AndroidRelease', - 'IosArm64', - 'IosSimulatorArm64', - 'IosX64', - 'Js', - 'Jvm', - 'KotlinMultiplatform', - 'WasmJs', - ] - exclude: # only publish ios targets from macos, everything else should be ubuntu - - { os: 'macos-latest', target: 'AndroidDebug' } - - { os: 'macos-latest', target: 'AndroidRelease' } - - { os: 'ubuntu-latest', target: 'IosArm64' } - - { os: 'ubuntu-latest', target: 'IosSimulatorArm64' } - - { os: 'ubuntu-latest', target: 'IosX64' } - - { os: 'macos-latest', target: 'Js' } - - { os: 'macos-latest', target: 'Jvm' } - - { os: 'macos-latest', target: 'KotlinMultiplatform' } - - { os: 'macos-latest', target: 'WasmJs' } - name: 'Publish ${{ matrix.target }} artifacts on ${{ matrix.os }} JDK ${{ matrix.java }}' - runs-on: '${{ matrix.os }}' + config: + - {target: 'AndroidDebug', os: 'ubuntu-latest' } + - {target: 'AndroidRelease', os: 'ubuntu-latest' } + - {target: 'IosArm64', os: 'macos-latest' } + - {target: 'IosSimulatorArm64', os: 'macos-latest' } + - {target: 'IosX64', os: 'macos-latest' } + - {target: 'Js', os: 'ubuntu-latest' } + - {target: 'Jvm', os: 'ubuntu-latest' } + - {target: 'KotlinMultiplatform', os: 'ubuntu-latest' } + - {target: 'WasmJs', os: 'ubuntu-latest' } + name: 'Publish ${{ matrix.config.target }} artifacts on ${{ matrix.config.os }} JDK ${{ matrix.java }}' + runs-on: '${{ matrix.config.os }}' needs: ['openStagingRepo'] env: stagingRepositoryId: ${{needs.openStagingRepo.outputs.stagingRepositoryId}} @@ -97,7 +85,7 @@ jobs: distribution: 'temurin' java-version: 17 - run: 'echo "stagingRepositoryId=$(echo $stagingRepositoryId)"' - - run: './gradlew publish${{ matrix.target }}PublicationToMavenCentralRepository --stacktrace -Prelease -PorchidEnvironment=prod' + - run: './gradlew publish${{ matrix.config.target }}PublicationToMavenCentralRepository --stacktrace -Prelease -PorchidEnvironment=prod' closeStagingRepo: runs-on: 'ubuntu-latest' diff --git a/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BaseBrowserNavigationInterceptor.kt b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BaseBrowserNavigationInterceptor.kt new file mode 100644 index 00000000..68a0e024 --- /dev/null +++ b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BaseBrowserNavigationInterceptor.kt @@ -0,0 +1,117 @@ +package com.copperleaf.ballast.navigation.browser + +import com.copperleaf.ballast.Queued +import com.copperleaf.ballast.awaitViewModelStart +import com.copperleaf.ballast.events +import com.copperleaf.ballast.navigation.internal.Uri +import com.copperleaf.ballast.navigation.internal.UriBuilder +import com.copperleaf.ballast.navigation.routing.Route +import com.copperleaf.ballast.navigation.routing.RouterContract +import com.copperleaf.ballast.navigation.routing.build +import com.copperleaf.ballast.navigation.routing.directions +import com.copperleaf.ballast.navigation.routing.mapCurrentDestination +import com.copperleaf.ballast.navigation.vm.RouterInterceptor +import com.copperleaf.ballast.navigation.vm.RouterInterceptorScope +import com.copperleaf.ballast.navigation.vm.RouterNotification +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch + +@Suppress("UNUSED_PARAMETER") +public abstract class BaseBrowserNavigationInterceptor internal constructor( + private val initialRoute: T +) : RouterInterceptor { + + internal abstract fun getInitialUrl(): Uri? + internal abstract fun watchForUrlChanges(): Flow + internal abstract fun setDestinationUrl(url: Uri) + + final override fun RouterInterceptorScope.start( + notifications: Flow> + ) { + launch(start = CoroutineStart.UNDISPATCHED) { + // start by setting the initial route from the browser hash, if provided when the webpage first loads + onViewModelInitSetStateFromBrowser(notifications) + + // then sync the hash state to the router state (in both directions) + joinAll( + updateBrowserOnStateChange(notifications), + updateStateOnBrowserChange(notifications) + ) + } + } + + private suspend fun RouterInterceptorScope.onViewModelInitSetStateFromBrowser( + notifications: Flow> + ) { + // wait for the BallastNotification.ViewModelStarted to be sent + notifications.awaitViewModelStart() + + val initialDestinationUrl = getInitialUrl()?.encodedPathAndQuery + ?: initialRoute.directions().build() + + val deferred = CompletableDeferred() + + sendToQueue( + Queued.HandleInput( + deferred, + RouterContract.Inputs.GoToDestination( + destination = initialDestinationUrl + ) + ) + ) + + // wait for the initial URL to be set in the state, before allowing the rest of the address bar syncing to begin + deferred.await() + } + + private fun RouterInterceptorScope.updateBrowserOnStateChange( + notifications: Flow> + ): Job = launch(start = CoroutineStart.UNDISPATCHED) { + notifications + .events { it } + .filterIsInstance>() + .mapNotNull { ev -> + ev.backstack.mapCurrentDestination( + route = { + if (annotations.any { it is WebEventRouteAnnotation }) { + // ignore this request + null + } else { + UriBuilder.parse(originalDestinationUrl) + } + }, + notFound = { UriBuilder.parse(it) }, + ) + } + .distinctUntilChanged() + .onEach { destination -> setDestinationUrl(destination) } + .launchIn(this) + } + + private fun RouterInterceptorScope.updateStateOnBrowserChange( + notifications: Flow> + ): Job = launch(start = CoroutineStart.UNDISPATCHED) { + watchForUrlChanges() + .onEach { destination -> + sendToQueue( + Queued.HandleInput( + null, + RouterContract.Inputs.GoToDestination( + destination = destination.encodedPathAndQuery, + extraAnnotations = setOf(WebEventRouteAnnotation), + ) + ) + ) + } + .launchIn(this) + } +} diff --git a/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHashNavigationInterceptor.kt b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHashNavigationInterceptor.kt new file mode 100644 index 00000000..4f88c952 --- /dev/null +++ b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHashNavigationInterceptor.kt @@ -0,0 +1,61 @@ +package com.copperleaf.ballast.navigation.browser + +import com.copperleaf.ballast.navigation.internal.Uri +import com.copperleaf.ballast.navigation.internal.UriBuilder +import com.copperleaf.ballast.navigation.routing.Route +import kotlinx.browser.window +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import org.w3c.dom.HashChangeEvent + +public class BrowserHashNavigationInterceptor( + initialRoute: T, +) : BaseBrowserNavigationInterceptor(initialRoute) { + + override fun getInitialUrl(): Uri? { + val hashValue = window.location.hash.trim().trimStart('#').trimStart('/') + val hashPieces = hashValue.split('?') + + val (initialPath: String?, initialQueryString: String?) = if (hashPieces.size == 2) { + // we have path and query in the hash value + val path = hashPieces[0].takeIf { it.isNotBlank() } + val query = hashPieces[1].takeIf { it.isNotBlank() } + path to query + } else { + // only have the path in the hash value + val path = hashPieces[0].takeIf { it.isNotBlank() } + val query = window.location.search.trimStart('?').takeIf { it.isNotBlank() } + path to query + } + + return if (!initialPath.isNullOrBlank() || !initialQueryString.isNullOrBlank()) { + UriBuilder.build( + encodedPath = "/$initialPath".also { println("initialPath: $it") }, + encodedQueryString = initialQueryString, + ) + } else { + null + } + } + + override fun watchForUrlChanges(): Flow { + return callbackFlow { + window.onhashchange = { event: HashChangeEvent -> + val partAfterHash = event.newURL?.split("#")?.last() + if (!partAfterHash.isNullOrBlank()) { + this@callbackFlow.trySend(UriBuilder.parse(partAfterHash)) + } + Unit + } + + awaitClose { + window.onhashchange = null + } + } + } + + override fun setDestinationUrl(url: Uri) { + window.location.hash = url.encodedPathAndQuery + } +} diff --git a/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHistoryNavigationInterceptor.kt b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHistoryNavigationInterceptor.kt new file mode 100644 index 00000000..e32bcf3c --- /dev/null +++ b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/BrowserHistoryNavigationInterceptor.kt @@ -0,0 +1,72 @@ +package com.copperleaf.ballast.navigation.browser + +import com.copperleaf.ballast.navigation.internal.Uri +import com.copperleaf.ballast.navigation.internal.UriBuilder +import com.copperleaf.ballast.navigation.routing.Route +import kotlinx.browser.window +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import org.w3c.dom.PopStateEvent + +// TODO: read the value of the tag to determine the base URL +public class BrowserHistoryNavigationInterceptor( + basePath: String? = null, + initialRoute: T, +) : BaseBrowserNavigationInterceptor(initialRoute) { + private val basePath: String? = basePath?.trim('/') + + override fun getInitialUrl(): Uri? { + val browserPath = window.location.pathname.trimStart('/') + val initialPath = if (basePath != null) { + browserPath.removePrefix(basePath).trimStart('/') + } else { + browserPath + } + val initialQueryString = window.location.search.trimStart('?').takeIf { it.isNotBlank() } + + return if (initialPath.isNotBlank() || !initialQueryString.isNullOrBlank()) { + UriBuilder.build( + encodedPath = initialPath, + encodedQueryString = initialQueryString, + ) + } else { + null + } + } + + override fun watchForUrlChanges(): Flow { + return callbackFlow { + window.onpopstate = { event: PopStateEvent -> + if(event.state != null) { + this@callbackFlow.trySend(UriBuilder.parse(event.state.toString())) + } + Unit + } + + awaitClose { + window.onpopstate = null + } + } + } + + override fun setDestinationUrl(url: Uri) { + try { + val previousDestination = getInitialUrl() + if (previousDestination != url) { + val updatedUrl = if(basePath != null) { + UriBuilder.build( + encodedPath = "${basePath}/${url.encodedPath.trim('/')}", + encodedQueryString = url.encodedQueryString, + ) + } else { + url + } + val serializedUrl = updatedUrl.encodedPathAndQuery + window.history.pushState(serializedUrl.toJsString(), "", serializedUrl) + } + } catch (e: Exception) { + e.printStackTrace() + } + } +} diff --git a/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/WebEventRouteAnnotation.kt b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/WebEventRouteAnnotation.kt new file mode 100644 index 00000000..74b88f3b --- /dev/null +++ b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/WebEventRouteAnnotation.kt @@ -0,0 +1,5 @@ +package com.copperleaf.ballast.navigation.browser + +import com.copperleaf.ballast.navigation.routing.RouteAnnotation + +internal object WebEventRouteAnnotation : RouteAnnotation diff --git a/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/browserUtils.kt b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/browserUtils.kt new file mode 100644 index 00000000..33204b46 --- /dev/null +++ b/ballast-navigation/src/wasmJsMain/kotlin/com/copperleaf/ballast/navigation/browser/browserUtils.kt @@ -0,0 +1,47 @@ +package com.copperleaf.ballast.navigation.browser + +import com.copperleaf.ballast.BallastViewModelConfiguration +import com.copperleaf.ballast.core.BootstrapInterceptor +import com.copperleaf.ballast.navigation.routing.Route +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder +import com.copperleaf.ballast.navigation.vm.withRouter +import com.copperleaf.ballast.plusAssign + +/** + * Configure a ViewModel to be used as a Router. This router will be set as your application's main router, + * synchronizing its state with the browser's address bar using the + * [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). [initialRoute] should be provided as a + * fallback for when a page is requested without an initial destination set in the URL. + * + * If [initialRoute] is provided, it will be automatically set as the + * initial route using a [BootstrapInterceptor]. You may wish to keep this value as `null` to load the initial route + * from some other means. + */ +public fun BallastViewModelConfiguration.Builder.withBrowserHistoryRouter( + routingTable: RoutingTable, + basePath: String? = null, + initialRoute: T, +): RouterBuilder { + return this + .withRouter(routingTable, initialRoute = null) + .apply { + this += BrowserHistoryNavigationInterceptor(basePath, initialRoute) + } +} + +/** + * Configure a ViewModel to be used as a Router. This router will be set as your application's main router, + * synchronizing its state with the browser's address bar using the hash-based routing. [initialRoute] should be + * provided as a fallback for when a page is requested without an initial destination set in the URL. + */ +public fun BallastViewModelConfiguration.Builder.withBrowserHashRouter( + routingTable: RoutingTable, + initialRoute: T, +): RouterBuilder { + return this + .withRouter(routingTable, initialRoute = null) + .apply { + this += BrowserHashNavigationInterceptor(initialRoute) + } +} diff --git a/examples/navigationWithEnumRoutes/src/androidMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.android.kt b/examples/navigationWithEnumRoutes/src/androidMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.android.kt index a01488fa..c3d63367 100644 --- a/examples/navigationWithEnumRoutes/src/androidMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.android.kt +++ b/examples/navigationWithEnumRoutes/src/androidMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.android.kt @@ -1,10 +1,13 @@ package com.copperleaf.ballast.examples.navigation -import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration import com.copperleaf.ballast.core.AndroidLogger +import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.debugger.BallastDebuggerClientConnection import com.copperleaf.ballast.debugger.BallastDebuggerInterceptor +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder +import com.copperleaf.ballast.navigation.vm.withRouter import com.copperleaf.ballast.plusAssign import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.CoroutineScope @@ -21,11 +24,22 @@ private val lazyConnection by lazy { }.also { it.connect() } } -internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder = - apply { +internal actual fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder { + return apply { + logger = ::AndroidLogger + this += LoggingInterceptor() + } +} + +internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder { + return apply { this += BallastDebuggerInterceptor(lazyConnection) } +} -internal actual fun platformLogger(loggerName: String): BallastLogger { - return AndroidLogger(loggerName) +internal actual fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder { + return withRouter(routingTable, initialRoute) } diff --git a/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/RouterViewModel.kt b/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/RouterViewModel.kt index 05f59a37..d637d1f4 100644 --- a/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/RouterViewModel.kt +++ b/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/RouterViewModel.kt @@ -2,14 +2,11 @@ package com.copperleaf.ballast.examples.navigation import com.copperleaf.ballast.BallastViewModelConfiguration import com.copperleaf.ballast.build -import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.eventHandler import com.copperleaf.ballast.navigation.routing.RoutingTable import com.copperleaf.ballast.navigation.routing.fromEnum import com.copperleaf.ballast.navigation.vm.BasicRouter import com.copperleaf.ballast.navigation.vm.Router -import com.copperleaf.ballast.navigation.vm.withRouter -import com.copperleaf.ballast.plusAssign import kotlinx.coroutines.CoroutineScope // Build VM @@ -18,21 +15,11 @@ import kotlinx.coroutines.CoroutineScope internal fun createRouter(viewModelCoroutineScope: CoroutineScope): Router { return BasicRouter( config = BallastViewModelConfiguration.Builder() - .logging() - .debugging() - .withRouter(RoutingTable.fromEnum(AppScreen.entries), AppScreen.Home) + .installLogging() + .installDebugger() + .installRouting(RoutingTable.fromEnum(AppScreen.entries), AppScreen.Home) .build(), eventHandler = eventHandler { }, coroutineScope = viewModelCoroutineScope, ) } - -private fun BallastViewModelConfiguration.Builder.logging(): BallastViewModelConfiguration.Builder = apply { - logger = ::platformLogger - this += LoggingInterceptor() -} - -private fun BallastViewModelConfiguration.Builder.debugging(): BallastViewModelConfiguration.Builder { - return installDebugger() -} - diff --git a/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/platformExpect.kt b/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/platformExpect.kt index 0917c21a..5e5a3824 100644 --- a/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/platformExpect.kt +++ b/examples/navigationWithEnumRoutes/src/commonMain/kotlin/com/copperleaf/ballast/examples/navigation/platformExpect.kt @@ -2,7 +2,14 @@ package com.copperleaf.ballast.examples.navigation import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder + +internal expect fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder internal expect fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder -internal expect fun platformLogger(loggerName: String): BallastLogger +internal expect fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder diff --git a/examples/navigationWithEnumRoutes/src/iosMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.ios.kt b/examples/navigationWithEnumRoutes/src/iosMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.ios.kt index c48dd846..ee8b7741 100644 --- a/examples/navigationWithEnumRoutes/src/iosMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.ios.kt +++ b/examples/navigationWithEnumRoutes/src/iosMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.ios.kt @@ -1,10 +1,13 @@ package com.copperleaf.ballast.examples.navigation -import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration +import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.core.OSLogLogger import com.copperleaf.ballast.debugger.BallastDebuggerClientConnection import com.copperleaf.ballast.debugger.BallastDebuggerInterceptor +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder +import com.copperleaf.ballast.navigation.vm.withRouter import com.copperleaf.ballast.plusAssign import io.ktor.client.engine.darwin.Darwin import kotlinx.cinterop.ExperimentalForeignApi @@ -23,12 +26,23 @@ private val lazyConnection by lazy { }.also { it.connect() } } -internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder = - apply { +@OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) +internal actual fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder { + return apply { + logger = ::OSLogLogger + this += LoggingInterceptor() + } +} + +internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder { + return apply { this += BallastDebuggerInterceptor(lazyConnection) } +} -@OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) -internal actual fun platformLogger(loggerName: String): BallastLogger { - return OSLogLogger(loggerName) +internal actual fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder { + return withRouter(routingTable, initialRoute) } diff --git a/examples/navigationWithEnumRoutes/src/jsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.js.kt b/examples/navigationWithEnumRoutes/src/jsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.js.kt index f0818414..93528215 100644 --- a/examples/navigationWithEnumRoutes/src/jsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.js.kt +++ b/examples/navigationWithEnumRoutes/src/jsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.js.kt @@ -1,10 +1,13 @@ package com.copperleaf.ballast.examples.navigation -import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration import com.copperleaf.ballast.core.JsConsoleLogger +import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.debugger.BallastDebuggerClientConnection import com.copperleaf.ballast.debugger.BallastDebuggerInterceptor +import com.copperleaf.ballast.navigation.browser.withBrowserHashRouter +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder import com.copperleaf.ballast.plusAssign import io.ktor.client.engine.js.Js import kotlinx.coroutines.CoroutineScope @@ -21,11 +24,22 @@ private val lazyConnection by lazy { }.also { it.connect() } } -internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder = - apply { +internal actual fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder { + return apply { + logger = ::JsConsoleLogger + this += LoggingInterceptor() + } +} + +internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder { + return apply { this += BallastDebuggerInterceptor(lazyConnection) } +} -internal actual fun platformLogger(loggerName: String): BallastLogger { - return JsConsoleLogger(loggerName) +internal actual fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder { + return withBrowserHashRouter(routingTable, initialRoute) } diff --git a/examples/navigationWithEnumRoutes/src/jvmMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.jvm.kt b/examples/navigationWithEnumRoutes/src/jvmMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.jvm.kt index 39670f5e..563fa03b 100644 --- a/examples/navigationWithEnumRoutes/src/jvmMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.jvm.kt +++ b/examples/navigationWithEnumRoutes/src/jvmMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.jvm.kt @@ -1,10 +1,13 @@ package com.copperleaf.ballast.examples.navigation -import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration +import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.core.PrintlnLogger import com.copperleaf.ballast.debugger.BallastDebuggerClientConnection import com.copperleaf.ballast.debugger.BallastDebuggerInterceptor +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder +import com.copperleaf.ballast.navigation.vm.withRouter import com.copperleaf.ballast.plusAssign import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.CoroutineScope @@ -21,11 +24,22 @@ private val lazyConnection by lazy { }.also { it.connect() } } -internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder = - apply { +internal actual fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder { + return apply { + logger = ::PrintlnLogger + this += LoggingInterceptor() + } +} + +internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder { + return apply { this += BallastDebuggerInterceptor(lazyConnection) } +} -internal actual fun platformLogger(loggerName: String): BallastLogger { - return PrintlnLogger(loggerName) +internal actual fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder { + return withRouter(routingTable, initialRoute) } diff --git a/examples/navigationWithEnumRoutes/src/wasmJsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.wasmJs.kt b/examples/navigationWithEnumRoutes/src/wasmJsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.wasmJs.kt index 99a3664f..18f0716f 100644 --- a/examples/navigationWithEnumRoutes/src/wasmJsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.wasmJs.kt +++ b/examples/navigationWithEnumRoutes/src/wasmJsMain/kotlin/com/copperleaf/ballast/examples/navigation/platformActual.wasmJs.kt @@ -1,14 +1,28 @@ package com.copperleaf.ballast.examples.navigation -import com.copperleaf.ballast.BallastLogger import com.copperleaf.ballast.BallastViewModelConfiguration +import com.copperleaf.ballast.core.LoggingInterceptor import com.copperleaf.ballast.core.WasmJsConsoleLogger +import com.copperleaf.ballast.navigation.browser.withBrowserHashRouter +import com.copperleaf.ballast.navigation.routing.RoutingTable +import com.copperleaf.ballast.navigation.vm.RouterBuilder +import com.copperleaf.ballast.plusAssign -internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder = - apply { - // debugger not yet available on wasmJs +internal actual fun BallastViewModelConfiguration.Builder.installLogging(): BallastViewModelConfiguration.Builder { + return apply { + logger = ::WasmJsConsoleLogger + this += LoggingInterceptor() } +} + +internal actual fun BallastViewModelConfiguration.Builder.installDebugger(): BallastViewModelConfiguration.Builder { + return apply { + } +} -internal actual fun platformLogger(loggerName: String): BallastLogger { - return WasmJsConsoleLogger(loggerName) +internal actual fun BallastViewModelConfiguration.Builder.installRouting( + routingTable: RoutingTable, + initialRoute: AppScreen, +): RouterBuilder { + return withBrowserHashRouter(routingTable, initialRoute) }