Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Auth, Settings モジュールを Compose Multiplatform 化 #44

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 4 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,14 @@
package club.nito.app.di

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

val featureModules: List<Module> = listOf(
authFeatureModule,
topFeatureModule,
scheduleFeatureModule,
settingsFeatureModule,
)
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
Loading