diff --git a/composeApp/src/androidMain/kotlin/SystemNavigation.android.kt b/composeApp/src/androidMain/kotlin/SystemNavigation.android.kt new file mode 100644 index 00000000..e572ff5b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/SystemNavigation.android.kt @@ -0,0 +1,8 @@ +import ui.navigation.Screen + +class AndroidSystemNavigation : SystemNavigation { + override fun navigateTo(screen: Screen) {} + override fun navigateBack() {} +} + +actual fun systemNavigation(): SystemNavigation = AndroidSystemNavigation() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/SystemNavigation.kt b/composeApp/src/commonMain/kotlin/SystemNavigation.kt new file mode 100644 index 00000000..19425ec8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/SystemNavigation.kt @@ -0,0 +1,8 @@ +import ui.navigation.Screen + +interface SystemNavigation { + fun navigateTo(screen: Screen) + fun navigateBack() +} + +expect fun systemNavigation(): SystemNavigation \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/AppModule.kt b/composeApp/src/commonMain/kotlin/di/AppModule.kt index 044cb534..5990ee28 100644 --- a/composeApp/src/commonMain/kotlin/di/AppModule.kt +++ b/composeApp/src/commonMain/kotlin/di/AppModule.kt @@ -4,6 +4,7 @@ import ivy.di.Di import ivy.di.Di.register import ivy.di.Di.singleton import ivy.di.DiModule +import systemNavigation import ui.navigation.Navigation import util.DispatchersProvider import util.DispatchersProviderImpl @@ -12,7 +13,8 @@ object AppModule : DiModule { override fun init() { Di.appScope { - singleton { Navigation() } + register { systemNavigation() } + singleton { Navigation(Di.get()) } register { DispatchersProviderImpl() } } } diff --git a/composeApp/src/commonMain/kotlin/ui/navigation/Navigation.kt b/composeApp/src/commonMain/kotlin/ui/navigation/Navigation.kt index a0dff3d0..a9c8b47c 100644 --- a/composeApp/src/commonMain/kotlin/ui/navigation/Navigation.kt +++ b/composeApp/src/commonMain/kotlin/ui/navigation/Navigation.kt @@ -1,11 +1,12 @@ package ui.navigation +import SystemNavigation import androidx.compose.runtime.* import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.plus import kotlinx.collections.immutable.toPersistentList -class Navigation { +class Navigation(private val systemNavigation: SystemNavigation) { private var backstack by mutableStateOf(persistentListOf()) @Composable @@ -18,6 +19,7 @@ class Navigation { fun navigate(screen: Screen) { backstack = backstack.plus(screen.also(Screen::initialize)) + systemNavigation.navigateTo(screen) } fun backUntil(predicate: (Screen) -> Boolean) { @@ -30,6 +32,9 @@ class Navigation { fun back(): Screen? { val lastScreen = backstack.lastOrNull() backstack = backstack.dropLast(1).toPersistentList() - return lastScreen?.also(Screen::destroy) + return lastScreen?.also { + it.destroy() + systemNavigation.navigateBack() + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/navigation/Screen.kt b/composeApp/src/commonMain/kotlin/ui/navigation/Screen.kt index 1b362f52..1d7b931a 100644 --- a/composeApp/src/commonMain/kotlin/ui/navigation/Screen.kt +++ b/composeApp/src/commonMain/kotlin/ui/navigation/Screen.kt @@ -9,6 +9,8 @@ import kotlinx.coroutines.SupervisorJob abstract class Screen { + abstract val path: String + private lateinit var job: CompletableJob protected lateinit var screenScope: CoroutineScope diff --git a/composeApp/src/commonMain/kotlin/ui/screen/debug/ColorDemoScreen.kt b/composeApp/src/commonMain/kotlin/ui/screen/debug/ColorDemoScreen.kt index 2295c7eb..1e20ae22 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/debug/ColorDemoScreen.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/debug/ColorDemoScreen.kt @@ -1,11 +1,7 @@ package ui.screen.debug import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material.Text @@ -18,15 +14,11 @@ import component.LearnScaffold import ivy.di.Di import ui.navigation.Navigation import ui.navigation.Screen -import ui.theme.Blue -import ui.theme.BlueVariant -import ui.theme.Gray -import ui.theme.Green -import ui.theme.Orange -import ui.theme.OrangeVariant -import ui.theme.Red +import ui.theme.* class ColorDemoScreen : Screen() { + override val path: String = "colorDemo" + private val navigation: Navigation = Di.get() override fun onDi(): Di.ScreenScope.() -> Unit = {} diff --git a/composeApp/src/commonMain/kotlin/ui/screen/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/ui/screen/home/HomeScreen.kt index 6f77a3bd..801b3813 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/home/HomeScreen.kt @@ -7,6 +7,8 @@ import ui.navigation.Screen import ui.screen.home.composable.HomeContent class HomeScreen : Screen() { + override val path: String = "home" + override fun onDi(): Di.ScreenScope.() -> Unit = { register { HomeViewModel(Di.get()) } } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/intro/IntroScreen.kt b/composeApp/src/commonMain/kotlin/ui/screen/intro/IntroScreen.kt index b7cd1048..4363750a 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/intro/IntroScreen.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/intro/IntroScreen.kt @@ -7,6 +7,8 @@ import ui.navigation.Screen import ui.screen.intro.composable.IntroContent class IntroScreen : Screen() { + override val path: String = "intro" + override fun onDi(): Di.ScreenScope.() -> Unit = { register { IntroViewModel(Di.get()) } } diff --git a/composeApp/src/desktopMain/kotlin/SystemNavigation.desktop.kt b/composeApp/src/desktopMain/kotlin/SystemNavigation.desktop.kt new file mode 100644 index 00000000..7d7650d4 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/SystemNavigation.desktop.kt @@ -0,0 +1,8 @@ +import ui.navigation.Screen + +class DesktopSystemNavigation : SystemNavigation { + override fun navigateTo(screen: Screen) {} + override fun navigateBack() {} +} + +actual fun systemNavigation(): SystemNavigation = DesktopSystemNavigation() \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/SystemNavigation.ios.kt b/composeApp/src/iosMain/kotlin/SystemNavigation.ios.kt new file mode 100644 index 00000000..cc37df57 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/SystemNavigation.ios.kt @@ -0,0 +1,9 @@ +import ui.navigation.Screen + +class IOSSystemNavigation : SystemNavigation { + override fun navigateTo(screen: Screen) {} + + override fun navigateBack() {} +} + +actual fun systemNavigation(): SystemNavigation = IOSSystemNavigation() \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/SystemNavigation.js.kt b/composeApp/src/jsMain/kotlin/SystemNavigation.js.kt new file mode 100644 index 00000000..3dc4e209 --- /dev/null +++ b/composeApp/src/jsMain/kotlin/SystemNavigation.js.kt @@ -0,0 +1,20 @@ +import kotlinx.browser.window +import ui.navigation.Screen + +class WebSystemNavigation : SystemNavigation { + override fun navigateTo(screen: Screen) { + // TODO: Temporary workaround until we support deep links + val path = "" // "/${screen.path}" + + val stateObject = js("({})") + // TODO: Temporary workaround until we support deep links + stateObject.screen = "" //screen.path + window.history.pushState(stateObject, "", path) + } + + override fun navigateBack() { + window.history.back() + } +} + +actual fun systemNavigation(): SystemNavigation = WebSystemNavigation() \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/main.js.kt b/composeApp/src/jsMain/kotlin/main.js.kt index 5dd3b80e..f8587807 100644 --- a/composeApp/src/jsMain/kotlin/main.js.kt +++ b/composeApp/src/jsMain/kotlin/main.js.kt @@ -1,6 +1,9 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.CanvasBasedWindow +import ivy.di.Di +import kotlinx.browser.window import org.jetbrains.skiko.wasm.onWasmReady +import ui.navigation.Navigation @OptIn(ExperimentalComposeUiApi::class) fun main() { @@ -9,4 +12,14 @@ fun main() { App() } } + setupBackNavigationHandler() +} + +fun setupBackNavigationHandler() { + window.addEventListener("popstate", { + val navigation = Di.get() + if (navigation.backstack().size > 1) { + navigation.back() + } + }) } \ No newline at end of file diff --git a/server/src/main/kotlin/ivy/learn/api/LessonsApi.kt b/server/src/main/kotlin/ivy/learn/api/LessonsApi.kt index d47bcc24..a042cb83 100644 --- a/server/src/main/kotlin/ivy/learn/api/LessonsApi.kt +++ b/server/src/main/kotlin/ivy/learn/api/LessonsApi.kt @@ -2,6 +2,7 @@ package ivy.learn.api import arrow.core.raise.ensureNotNull import io.ktor.server.routing.* +import io.ktor.util.* import ivy.learn.api.common.Api import ivy.learn.api.common.endpoint import ivy.learn.api.common.model.ServerError @@ -13,7 +14,11 @@ class LessonsApi( private val repository: LessonsRepository ) : Api { override fun Routing.endpoints() { - // Endpoint that gets a lesson by ID + lessonById() + } + + @KtorDsl + private fun Routing.lessonById() { get("/lessons/{id}", endpoint { params -> val lessonId = params["id"]?.let(::LessonId) ensureNotNull(lessonId) { ServerError.BadRequest("Lesson id is missing!") } diff --git a/shared/src/androidMain/kotlin/Platform.android.kt b/shared/src/androidMain/kotlin/Platform.android.kt index d6cdd003..6a131d59 100644 --- a/shared/src/androidMain/kotlin/Platform.android.kt +++ b/shared/src/androidMain/kotlin/Platform.android.kt @@ -6,7 +6,6 @@ import io.ktor.client.engine.android.* class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" - override val debug: Boolean = false override fun log(level: LogLevel, msg: String) { val tag = "" diff --git a/shared/src/commonMain/kotlin/Platform.kt b/shared/src/commonMain/kotlin/Platform.kt index 59ea9921..5ac477e1 100644 --- a/shared/src/commonMain/kotlin/Platform.kt +++ b/shared/src/commonMain/kotlin/Platform.kt @@ -2,7 +2,6 @@ import io.ktor.client.* interface Platform { val name: String - val debug: Boolean fun log(level: LogLevel, msg: String) fun httpClient(config: HttpClientConfig<*>.() -> Unit = {}): HttpClient } diff --git a/shared/src/iosMain/kotlin/Platform.ios.kt b/shared/src/iosMain/kotlin/Platform.ios.kt index 76055538..6230cd66 100644 --- a/shared/src/iosMain/kotlin/Platform.ios.kt +++ b/shared/src/iosMain/kotlin/Platform.ios.kt @@ -4,7 +4,6 @@ import platform.UIKit.UIDevice class IOSPlatform : Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion - override val debug: Boolean = false override fun log(level: LogLevel, msg: String) { println("${level.name}: $msg") diff --git a/shared/src/jsMain/kotlin/Platform.js.kt b/shared/src/jsMain/kotlin/Platform.js.kt index f585ec1a..e85b826b 100644 --- a/shared/src/jsMain/kotlin/Platform.js.kt +++ b/shared/src/jsMain/kotlin/Platform.js.kt @@ -3,7 +3,6 @@ import io.ktor.client.engine.js.* class JsPlatform : Platform { override val name: String = "Web with Kotlin/JS" - override val debug: Boolean = false override fun log(level: LogLevel, msg: String) { console.log("${level.name}: $msg") diff --git a/shared/src/jvmMain/kotlin/Platform.jvm.kt b/shared/src/jvmMain/kotlin/Platform.jvm.kt index 730ef288..ac36d27d 100644 --- a/shared/src/jvmMain/kotlin/Platform.jvm.kt +++ b/shared/src/jvmMain/kotlin/Platform.jvm.kt @@ -3,7 +3,6 @@ import io.ktor.client.engine.java.* class JVMPlatform: Platform { override val name: String = "Java ${System.getProperty("java.version")}" - override val debug: Boolean = false override fun log(level: LogLevel, msg: String) { println("${level.name}: $msg")