Skip to content

Commit

Permalink
✨ Auth モジュールを Compose Multiplatform 化
Browse files Browse the repository at this point in the history
  • Loading branch information
tatsutakein committed Nov 22, 2023
1 parent 95a09da commit 2e9b2a4
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 116 deletions.
16 changes: 8 additions & 8 deletions app/android/src/main/java/club/nito/app/NitoNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import club.nito.core.model.AuthStatus
import club.nito.feature.auth.navigateToSignIn
import club.nito.feature.auth.signInNavigationRoute
import club.nito.feature.auth.signInScreen
import club.nito.feature.auth.loginNavigationRoute
import club.nito.feature.auth.loginScreen
import club.nito.feature.auth.navigateToLogin
import club.nito.feature.schedule.navigateToSchedule
import club.nito.feature.schedule.scheduleScreen
import club.nito.feature.settings.navigateToSettings
Expand Down Expand Up @@ -45,11 +45,11 @@ fun NitoNavHost(
onScheduleListClick = navController::navigateToSchedule,
onSettingsClick = navController::navigateToSettings,
)
signInScreen(
onSignedIn = {
loginScreen(
onLoggedIn = {
navController.navigateToTop(
navOptions = navOptions {
popUpTo(signInNavigationRoute) {
popUpTo(loginNavigationRoute) {
inclusive = true
}
},
Expand All @@ -59,7 +59,7 @@ fun NitoNavHost(
scheduleScreen()
settingsScreen(
onSignedOut = {
navController.navigateToSignIn(
navController.navigateToLogin(
navOptions = navOptions {
popUpTo(topNavigationRoute) {
inclusive = true
Expand All @@ -77,7 +77,7 @@ private fun NavGraphBuilder.root(
) = composable(rootNavigationRoute) {
LaunchedEffect(authStatus) {
when (authStatus) {
AuthStatus.NotAuthenticated -> navController.navigateToSignIn(
AuthStatus.NotAuthenticated -> navController.navigateToLogin(
navOptions = navOptions {
popUpTo(rootNavigationRoute) {
inclusive = true
Expand Down
2 changes: 2 additions & 0 deletions app/android/src/main/java/club/nito/app/di/FeatureModules.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package club.nito.app.di

import club.nito.feature.auth.di.authFeatureModule
import club.nito.feature.schedule.di.scheduleFeatureModule
import club.nito.feature.top.di.topFeatureModule
import org.koin.core.module.Module

val featureModules: List<Module> = listOf(
authFeatureModule,
topFeatureModule,
scheduleFeatureModule,
)
3 changes: 3 additions & 0 deletions feature/auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ kotlin {
implementation(projects.core.designsystem)

implementation(libs.kotlinxCoroutinesCore)

implementation(libs.koin)
implementation(libs.koinCompose)
}
}
androidMain {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package club.nito.feature.auth

import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable

fun NavController.navigateToLogin(navOptions: NavOptions? = null) {
this.navigate(loginNavigationRoute, navOptions)
}

fun NavGraphBuilder.loginScreen(
onLoggedIn: () -> Unit = {},
onRegisterClick: () -> Unit = {},
) {
composable(
route = loginNavigationRoute,
) {
LoginRoute(
onLoggedIn = onLoggedIn,
onRegisterClick = onRegisterClick,
)
}
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package club.nito.feature.auth

import moe.tlaster.precompose.navigation.NavOptions
import moe.tlaster.precompose.navigation.Navigator
import moe.tlaster.precompose.navigation.RouteBuilder

const val loginNavigationRoute = "login_route"

fun Navigator.navigateToLogin(navOptions: NavOptions? = null) {
this.navigate(loginNavigationRoute, navOptions)
}

fun RouteBuilder.loginScreen(
onLoggedIn: () -> Unit = {},
onRegisterClick: () -> Unit = {},
) {
scene(
route = loginNavigationRoute,
) {
LoginRoute(
onLoggedIn = onLoggedIn,
onRegisterClick = onRegisterClick,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import club.nito.core.designsystem.component.CenterAlignedTopAppBar
import club.nito.core.designsystem.component.Scaffold
import club.nito.core.designsystem.component.Text
import club.nito.core.ui.koinStateMachine
import club.nito.core.ui.message.SnackbarMessageEffect

@Composable
fun SignInRoute(
viewModel: SignInViewModel = hiltViewModel(),
onSignedIn: () -> Unit = {},
fun LoginRoute(
viewModel: LoginScreenStateMachine = koinStateMachine(),
onLoggedIn: () -> Unit = {},
onRegisterClick: () -> Unit = {},
) {
viewModel.event.collectAsState(initial = null).value?.let {
LaunchedEffect(it.hashCode()) {
when (it) {
SignInEvent.NavigateToRegister -> onRegisterClick()
SignInEvent.SignedIn -> onSignedIn()
LoginScreenEvent.NavigateToRegister -> onRegisterClick()
LoginScreenEvent.LoggedIn -> onLoggedIn()
}
viewModel.consume(it)
}
Expand All @@ -50,7 +50,7 @@ fun SignInRoute(
userMessageStateHolder = viewModel.userMessageStateHolder,
)

SignInScreen(
LoginScreen(
uiState = uiState,
snackbarHostState = snackbarHostState,
dispatch = viewModel::dispatch,
Expand All @@ -59,10 +59,10 @@ fun SignInRoute(

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SignInScreen(
uiState: SignInScreenUiState,
private fun LoginScreen(
uiState: LoginScreenUiState,
snackbarHostState: SnackbarHostState,
dispatch: (SignInIntent) -> Unit = {},
dispatch: (LoginScreenIntent) -> Unit = {},
) {
Scaffold(
topBar = {
Expand All @@ -85,7 +85,7 @@ private fun SignInScreen(
) {
OutlinedTextField(
value = uiState.email,
onValueChange = { dispatch(SignInIntent.ChangeInputEmail(it)) },
onValueChange = { dispatch(LoginScreenIntent.ChangeInputEmail(it)) },
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Email") },
placeholder = { Text(text = "[email protected]") },
Expand All @@ -97,7 +97,7 @@ private fun SignInScreen(
)
OutlinedTextField(
value = uiState.password,
onValueChange = { dispatch(SignInIntent.ChangeInputPassword(it)) },
onValueChange = { dispatch(LoginScreenIntent.ChangeInputPassword(it)) },
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Password") },
placeholder = { Text(text = "password") },
Expand All @@ -111,7 +111,7 @@ private fun SignInScreen(
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = { dispatch(SignInIntent.ClickSignIn) },
onClick = { dispatch(LoginScreenIntent.ClickSignIn) },
enabled = uiState.isSignInButtonEnabled,
) {
Text(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package club.nito.feature.auth

sealed class LoginScreenEvent {
data object LoggedIn : LoginScreenEvent()
data object NavigateToRegister : LoginScreenEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package club.nito.feature.auth

sealed class LoginScreenIntent {
data class ChangeInputEmail(val email: String) : LoginScreenIntent()
data class ChangeInputPassword(val password: String) : LoginScreenIntent()
data object ClickSignIn : LoginScreenIntent()
data object ClickRegister : LoginScreenIntent()
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package club.nito.feature.auth

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.domain.SignInUseCase
import club.nito.core.model.AuthStatus
import club.nito.core.model.ExecuteResult
import club.nito.core.model.FetchSingleResult
import club.nito.core.ui.StateMachine
import club.nito.core.ui.buildUiState
import club.nito.core.ui.message.UserMessageStateHolder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
Expand All @@ -18,68 +16,66 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class SignInViewModel @Inject constructor(
class LoginScreenStateMachine internal constructor(
observeAuthStatusUseCase: ObserveAuthStatusUseCase,
private val signInUseCase: SignInUseCase,
val userMessageStateHolder: UserMessageStateHolder,
) : ViewModel(),
) : StateMachine(),
UserMessageStateHolder by userMessageStateHolder {

private val email = MutableStateFlow("")
private val password = MutableStateFlow("")
private val authStatus = observeAuthStatusUseCase().stateIn(
scope = viewModelScope,
scope = stateMachineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FetchSingleResult.Loading,
)

val uiState: StateFlow<SignInScreenUiState> = buildUiState(
val uiState: StateFlow<LoginScreenUiState> = buildUiState(
email,
password,
authStatus,
) { email, password, authStatus ->
SignInScreenUiState(
LoginScreenUiState(
email = email,
password = password,
isSignInning = authStatus is FetchSingleResult.Loading,
)
}

private val _events = MutableStateFlow<List<SignInEvent>>(emptyList())
val event: Flow<SignInEvent?> = _events.map { it.firstOrNull() }
private val _events = MutableStateFlow<List<LoginScreenEvent>>(emptyList())
val event: Flow<LoginScreenEvent?> = _events.map { it.firstOrNull() }

init {
viewModelScope.launch {
stateMachineScope.launch {
authStatus.collectLatest {
if (it is FetchSingleResult.Success && it.data is AuthStatus.Authenticated) {
_events.emit(listOf(SignInEvent.SignedIn))
_events.emit(listOf(LoginScreenEvent.LoggedIn))
}
}
}
}

fun dispatch(intent: SignInIntent) {
viewModelScope.launch {
fun dispatch(intent: LoginScreenIntent) {
stateMachineScope.launch {
when (intent) {
is SignInIntent.ChangeInputEmail -> email.emit(intent.email)
is SignInIntent.ChangeInputPassword -> password.emit(intent.password)
SignInIntent.ClickSignIn -> {
is LoginScreenIntent.ChangeInputEmail -> email.emit(intent.email)
is LoginScreenIntent.ChangeInputPassword -> password.emit(intent.password)
LoginScreenIntent.ClickSignIn -> {
val result = signInUseCase(email.value, password.value)
if (result is ExecuteResult.Failure) {
userMessageStateHolder.showMessage("ログインに失敗しました")
}
}

SignInIntent.ClickRegister -> {}
LoginScreenIntent.ClickRegister -> {}
}
}
}

fun consume(event: SignInEvent) {
viewModelScope.launch {
fun consume(event: LoginScreenEvent) {
stateMachineScope.launch {
_events.emit(_events.value.filterNot { it == event })
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package club.nito.feature.auth

data class SignInScreenUiState(
data class LoginScreenUiState(
val email: String,
val password: String,
val isSignInning: Boolean,
Expand Down

This file was deleted.

Loading

0 comments on commit 2e9b2a4

Please sign in to comment.