diff --git a/core/common-ui/src/main/java/com/puzzle/common/ui/CoroutineUtil.kt b/core/common-ui/src/main/java/com/puzzle/common/ui/CoroutineUtil.kt new file mode 100644 index 00000000..6d989fd6 --- /dev/null +++ b/core/common-ui/src/main/java/com/puzzle/common/ui/CoroutineUtil.kt @@ -0,0 +1,14 @@ +package com.puzzle.common.ui + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +fun LifecycleOwner.repeatOnStarted(block: suspend CoroutineScope.() -> Unit) { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block) + } +} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt index ca27a031..b95d8818 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt @@ -158,6 +158,7 @@ fun PieceLoginButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + border: BorderStroke? = null, containerColor: Color = PieceTheme.colors.primaryDefault, labelColor: Color = PieceTheme.colors.black ) { @@ -165,6 +166,7 @@ fun PieceLoginButton( onClick = onClick, enabled = enabled, shape = RoundedCornerShape(8.dp), + border = border, colors = ButtonDefaults.buttonColors( containerColor = containerColor, contentColor = PieceTheme.colors.white, diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index b9f343bd..18ff0f04 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ 매칭 수락하기 + 서로의 빈 곳을 채우며 맞물리는 퍼즐처럼.\n서로의 가치관과 마음이 연결되는 순간을 만들어갑니다. 카카오로 시작하기 구글로 시작하기 diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index d139dad7..fb851e0b 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -7,5 +7,6 @@ android { } dependencies { + implementation(projects.core.common) implementation(libs.kakao.user) } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginScreen.kt index cca8ef2f..51c16631 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginScreen.kt @@ -1,19 +1,19 @@ package com.puzzle.auth.graph.login +import android.content.Context +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -26,11 +26,14 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LocalLifecycleOwner import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import com.kakao.sdk.user.UserApiClient import com.puzzle.auth.graph.login.contract.LoginIntent.Navigate +import com.puzzle.auth.graph.login.contract.LoginSideEffect import com.puzzle.auth.graph.login.contract.LoginState +import com.puzzle.common.ui.repeatOnStarted import com.puzzle.designsystem.R import com.puzzle.designsystem.component.PieceLoginButton import com.puzzle.designsystem.component.PieceSubCloseTopBar @@ -40,29 +43,32 @@ import com.puzzle.navigation.AuthGraphDest import com.puzzle.navigation.NavigationEvent @Composable -fun LoginRoute( +internal fun LoginRoute( viewModel: LoginViewModel = mavericksViewModel(), ) { val state by viewModel.collectAsState() val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current - LoginScreen( - state = state, - loginKakao = { - UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> - if (error != null) { -// Log.e("test", "로그인 실패", error) - } else if (token != null) { -// Log.i("test", "로그인 성공 ${token.accessToken}") + LaunchedEffect(Unit) { + lifecycleOwner.repeatOnStarted { + viewModel.sideEffects.collect { sideEffect -> + when (sideEffect) { + is LoginSideEffect.LoginKakao -> loginKakao(context) } } - }, + } + } + + LoginScreen( + state = state, + loginKakao = { viewModel.onSideEffect(LoginSideEffect.LoginKakao) }, navigate = { viewModel.onIntent(Navigate(it)) }, ) } @Composable -fun LoginScreen( +private fun LoginScreen( state: LoginState, loginKakao: () -> Unit, navigate: (NavigationEvent) -> Unit, @@ -85,13 +91,9 @@ fun LoginScreen( PieceSubCloseTopBar( title = "", onCloseClick = { }, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 14.dp), + modifier = Modifier.fillMaxWidth(), ) - Spacer(modifier = Modifier.height(20.dp)) - Text( text = buildAnnotatedString { withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) { @@ -102,20 +104,20 @@ fun LoginScreen( }, style = PieceTheme.typography.headingLSB, color = PieceTheme.colors.black, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp, bottom = 12.dp), ) - Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "서로의 빈 곳을 채우며 맞물리는 퍼즐처럼.\n서로의 가치관과 마음이 연결되는 순간을 만들어갑니다.", + text = stringResource(R.string.login_description), style = PieceTheme.typography.bodySM, color = PieceTheme.colors.dark3, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 70.dp), ) - Spacer(modifier = Modifier.height(70.dp)) - Image( painter = painterResource(R.drawable.ic_puzzle1), contentDescription = null, @@ -129,34 +131,52 @@ fun LoginScreen( PieceLoginButton( label = stringResource(R.string.kakao_login), imageId = R.drawable.ic_kakao_login, - onClick = {}, containerColor = Color(0xFFFFE812), - modifier = Modifier.fillMaxWidth() + onClick = loginKakao, + modifier = Modifier.fillMaxWidth(), ) - Spacer(modifier = Modifier.height(10.dp)) - PieceLoginButton( label = stringResource(R.string.google_login), imageId = R.drawable.ic_google_login, - onClick = {}, containerColor = PieceTheme.colors.white, + border = BorderStroke( + width = 1.dp, + color = PieceTheme.colors.light1, + ), + onClick = {}, modifier = Modifier .fillMaxWidth() - .border( - width = 1.dp, - color = PieceTheme.colors.light1, - shape = RoundedCornerShape(8.dp) - ), + .padding(vertical = 10.dp), ) + } +} - Spacer(modifier = Modifier.height(10.dp)) +private fun loginKakao(context: Context) { + UserApiClient.instance.apply { + if (isKakaoTalkLoginAvailable(context)) { + loginWithKakaoAccount(context) { token, error -> + if (error != null) { +// Log.e("test", "로그인 실패", error) + } else if (token != null) { + // Log.i("test", "로그인 성공 ${token.accessToken}") + } + } + } else { + loginWithKakaoAccount(context) { token, error -> + if (error != null) { +// Log.e("test", "로그인 실패", error) + } else if (token != null) { +// Log.i("test", "로그인 성공 ${token.accessToken}") + } + } + } } } @Preview @Composable -fun PreviewAuthScreen() { +private fun PreviewAuthScreen() { PieceTheme { LoginScreen( state = LoginState(), diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginViewModel.kt index b97089d0..c3e965b5 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/login/LoginViewModel.kt @@ -24,8 +24,8 @@ class LoginViewModel @AssistedInject constructor( ) : MavericksViewModel(initialState) { private val intents = Channel(BUFFERED) - private val _sideEffect = Channel(BUFFERED) - val sideEffect = _sideEffect.receiveAsFlow() + private val _sideEffects = Channel(BUFFERED) + val sideEffects = _sideEffects.receiveAsFlow() init { intents.receiveAsFlow() @@ -43,10 +43,8 @@ class LoginViewModel @AssistedInject constructor( } } - private fun handleSideEffect(sideEffect: LoginSideEffect) { - when (sideEffect) { - else -> Unit - } + internal fun onSideEffect(sideEffect: LoginSideEffect) = viewModelScope.launch { + _sideEffects.send(sideEffect) } @AssistedFactory diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/login/contract/LoginSideEffect.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/login/contract/LoginSideEffect.kt index e05bd407..6854fddf 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/login/contract/LoginSideEffect.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/login/contract/LoginSideEffect.kt @@ -1,3 +1,5 @@ package com.puzzle.auth.graph.login.contract -sealed class LoginSideEffect \ No newline at end of file +sealed class LoginSideEffect { + data object LoginKakao : LoginSideEffect() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/puzzle/presentation/MainActivity.kt b/presentation/src/main/java/com/puzzle/presentation/MainActivity.kt index 6057f1d5..a5c3446a 100644 --- a/presentation/src/main/java/com/puzzle/presentation/MainActivity.kt +++ b/presentation/src/main/java/com/puzzle/presentation/MainActivity.kt @@ -9,12 +9,11 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.core.view.WindowCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.puzzle.common.event.PieceEvent +import com.puzzle.common.ui.repeatOnStarted import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.navigation.MatchingGraph import com.puzzle.navigation.NavigationEvent @@ -42,7 +41,7 @@ class MainActivity : ComponentActivity() { snackBarHostState = remember { SnackbarHostState() } LaunchedEffect(Unit) { - repeatOnLifecycle(Lifecycle.State.STARTED) { + repeatOnStarted { launch { navigationHelper.navigationFlow.collect { event -> handleNavigationEvent(