Skip to content

Commit

Permalink
Signup (#51)
Browse files Browse the repository at this point in the history
* WIP: Google auth

* Implement Google auth on BE side

* Login with Google POC

* WIP: Fix navigation

* Working webpack config

* Remove unnecessary code

* Fix nav

* Fix SPA on GitHub Pages

* WIP: Rework navigation

* Add fake Google auth

* WIP: Fix navigation

* Fix missing routing

* Refactor

* Refactor

* Refactor

* Fix build
  • Loading branch information
ILIYANGERMANOV authored Nov 27, 2024
1 parent 5ff3610 commit 8768fb6
Show file tree
Hide file tree
Showing 42 changed files with 538 additions and 153 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/deploy_web_github_pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ jobs:
- name: Build Web distribution
run: ./gradlew :composeApp:jsBrowserDistribution


- name: Copy index.html to 404.html
run: cp composeApp/build/dist/js/productionExecutable/index.html composeApp/build/dist/js/productionExecutable/404.html

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
16 changes: 9 additions & 7 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import data.di.DataModule
import di.AppModule
import domain.di.DomainModule
import ivy.di.Di
import ivy.di.Di.register
import ivy.di.SharedModule
import navigation.Navigation
import org.jetbrains.compose.ui.tooling.preview.Preview
import ui.navigation.Navigation
import ui.screen.intro.IntroScreen
import ui.theme.LearnTheme

@Composable
@Preview
fun App() {
var initialized by mutableStateOf(false)
val uriHandler = LocalUriHandler.current

LaunchedEffect(Unit) {
Di.init(
modules = setOf(
SharedModule,
AppModule,
DataModule,
DomainModule,
)
)
Di.appScope {
register { uriHandler }
}
initialized = true
}

Expand All @@ -37,11 +44,6 @@ fun App() {
@Composable
private fun NavGraph() {
val navigation = remember { Di.get<Navigation>() }
LaunchedEffect(navigation) {
// navigate to the initial screen
navigation.navigate(IntroScreen())
}

Box(modifier = Modifier.fillMaxSize()) {
navigation.NavHost()
}
Expand Down
8 changes: 0 additions & 8 deletions composeApp/src/commonMain/kotlin/SystemNavigation.kt

This file was deleted.

11 changes: 1 addition & 10 deletions composeApp/src/commonMain/kotlin/data/di/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import data.LessonRepository
import data.LessonRepositoryImpl
import data.TopicsRepository
import data.fake.FakeLessonRepository
import di.bindWithFake
import ivy.di.Di
import ivy.di.Di.register
import ivy.di.autowire.autoWire
Expand All @@ -25,14 +26,4 @@ object DataModule : Di.Module {
}
bindWithFake<LessonRepository, LessonRepositoryImpl, FakeLessonRepository>()
}
}

inline fun <reified Base : Any, reified Impl : Base, reified Fake : Base> Di.Scope.bindWithFake() {
register<Base> {
if (Di.get<AppConfiguration>().fakesEnabled) {
Di.get<Fake>()
} else {
Di.get<Impl>()
}
}
}
20 changes: 18 additions & 2 deletions composeApp/src/commonMain/kotlin/di/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package di

import AppConfiguration
import ivy.data.HerokuServerUrlProvider
import ivy.data.LocalServerUrlProvider
import ivy.data.ServerUrlProvider
import ivy.di.Di
import ivy.di.Di.register
import ivy.di.autowire.autoWireSingleton
import systemNavigation
import ui.navigation.Navigation
import navigation.Navigation
import navigation.systemNavigation
import util.DispatchersProvider
import util.DispatchersProviderImpl

Expand All @@ -14,7 +18,19 @@ object AppModule : Di.Module {
Di.appScope {
register { systemNavigation() }
autoWireSingleton(::Navigation)
autoWireSingleton(::AppConfiguration)
register<DispatchersProvider> { DispatchersProviderImpl() }
bindWithFake<ServerUrlProvider, HerokuServerUrlProvider, LocalServerUrlProvider>()
}
}
}

inline fun <reified Base : Any, reified Impl : Base, reified Fake : Base> Di.Scope.bindWithFake() {
register<Base> {
if (Di.get<AppConfiguration>().fakesEnabled) {
Di.get<Fake>()
} else {
Di.get<Impl>()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package domain

import IvyConstants
import androidx.compose.ui.platform.UriHandler
import ivy.data.ServerUrlProvider

class GoogleAuthenticationUseCaseImpl(
private val uriHandler: UriHandler,
private val serverUrlProvider: ServerUrlProvider,
) : GoogleAuthenticationUseCase {
override fun loginWithGoogle() {
val clientId = IvyConstants.GoogleClientId
val redirectUri = "${serverUrlProvider.serverUrl}${IvyConstants.GoogleAuthCallbackEndpoint}"

val authUrl = """
https://accounts.google.com/o/oauth2/v2/auth?
client_id=$clientId&
redirect_uri=$redirectUri&
response_type=code&
scope=email profile
""".trimIndent()

uriHandler.openUri(authUrl)
}
}

interface GoogleAuthenticationUseCase {
fun loginWithGoogle()
}
16 changes: 16 additions & 0 deletions composeApp/src/commonMain/kotlin/domain/di/DomainModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package domain.di

import di.bindWithFake
import domain.GoogleAuthenticationUseCase
import domain.GoogleAuthenticationUseCaseImpl
import domain.fake.FakeGoogleAuthenticationUseCase
import ivy.di.Di
import ivy.di.autowire.autoWire

object DomainModule : Di.Module {
override fun init() = Di.appScope {
autoWire(::GoogleAuthenticationUseCaseImpl)
autoWire(::FakeGoogleAuthenticationUseCase)
bindWithFake<GoogleAuthenticationUseCase, GoogleAuthenticationUseCaseImpl, FakeGoogleAuthenticationUseCase>()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domain.fake

import domain.GoogleAuthenticationUseCase
import navigation.Navigation
import ui.screen.home.HomeScreen

class FakeGoogleAuthenticationUseCase(
private val navigation: Navigation
) : GoogleAuthenticationUseCase {
override fun loginWithGoogle() {
navigation.navigateTo(HomeScreen())
}
}
34 changes: 34 additions & 0 deletions composeApp/src/commonMain/kotlin/navigation/Navigation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package navigation

import Platform
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import ui.screen.NotFoundPage

class Navigation(
private val systemNavigation: SystemNavigation,
private val platform: Platform,
) {
@Composable
fun NavHost() {
val currentRoute by systemNavigation.currentRoute.collectAsState()
val screen = remember(currentRoute) {
Routing.resolve(currentRoute)?.also(Screen::initialize)
}
screen?.Content() ?: NotFoundPage(currentRoute)
}

fun navigateTo(screen: Screen) {
systemNavigation.navigateTo(screen)
}

fun replaceWith(screen: Screen) {
systemNavigation.replaceWith(screen)
}

fun navigateBack() {
systemNavigation.navigateBack()
}
}
27 changes: 27 additions & 0 deletions composeApp/src/commonMain/kotlin/navigation/Routing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package navigation

import arrow.core.Option
import ui.screen.course.CourseRouter
import ui.screen.home.HomeRouter
import ui.screen.intro.IntroRouter
import ui.screen.lesson.LessonRouter

object Routing {
private val routers = setOf<Router<*>>(
IntroRouter,
HomeRouter,
LessonRouter,
CourseRouter,
)

fun resolve(route: Route): Screen? {
return routers.firstNotNullOfOrNull {
it.fromRoute(route).getOrNull()
}
}
}

interface Router<S : Screen> {
fun fromRoute(route: Route): Option<S>
fun toRoute(screen: S): Route
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ui.navigation
package navigation

import androidx.compose.runtime.Composable
import ivy.di.Di
Expand All @@ -10,22 +10,28 @@ import kotlinx.coroutines.SupervisorJob

abstract class Screen {

abstract val path: String

private lateinit var job: CompletableJob
protected lateinit var screenScope: CoroutineScope

protected abstract fun onDi(): Di.Scope.() -> Unit
private var initialized = false

abstract fun toRoute(): Route

protected abstract fun Di.Scope.onDi()

fun initialize() {
if (initialized) return

job = SupervisorJob()
screenScope = CoroutineScope(Dispatchers.Main + job)
onDi().invoke(FeatureScope)
FeatureScope.onDi()
initialized = true
}

fun destroy() {
job.cancel()
Di.clear(FeatureScope)
initialized = false
}


Expand Down
26 changes: 26 additions & 0 deletions composeApp/src/commonMain/kotlin/navigation/SystemNavigation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package navigation

import androidx.compose.runtime.Immutable
import arrow.core.None
import arrow.core.Option
import arrow.core.some
import kotlinx.coroutines.flow.StateFlow

interface SystemNavigation {
val currentRoute: StateFlow<Route>
fun navigateTo(screen: Screen)
fun replaceWith(screen: Screen)
fun navigateBack()
}

@Immutable
data class Route(
val path: String,
val params: Map<String, String> = emptyMap(),
) {
operator fun get(key: String): Option<String> {
return params[key]?.some() ?: None
}
}

expect fun systemNavigation(): SystemNavigation
44 changes: 0 additions & 44 deletions composeApp/src/commonMain/kotlin/ui/navigation/Navigation.kt

This file was deleted.

Loading

0 comments on commit 8768fb6

Please sign in to comment.