Skip to content

Commit

Permalink
Merge pull request #44 from 2rabs/rt/feature-compose
Browse files Browse the repository at this point in the history
✨ Auth, Settings モジュールを Compose Multiplatform 化
  • Loading branch information
tatsutakein authored Nov 22, 2023
2 parents 6ec6224 + 525fb38 commit 3dba00d
Show file tree
Hide file tree
Showing 36 changed files with 213 additions and 201 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
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

0 comments on commit 3dba00d

Please sign in to comment.