diff --git a/app/src/main/java/com/paw/key/data/di/AppModule.kt b/app/src/main/java/com/paw/key/data/di/AppModule.kt index fcb2ad21..9b55dc7d 100644 --- a/app/src/main/java/com/paw/key/data/di/AppModule.kt +++ b/app/src/main/java/com/paw/key/data/di/AppModule.kt @@ -2,6 +2,7 @@ package com.paw.key.data.di import android.content.ContentResolver import android.content.Context +import androidx.credentials.CredentialManager import com.paw.key.BuildConfig import dagger.Module import dagger.Provides @@ -26,4 +27,16 @@ object AppModule { fun provideContentResolver(@ApplicationContext context: Context): ContentResolver { return context.contentResolver } + + @Provides + @Singleton + fun provideContext(@ApplicationContext context: Context): Context { + return context + } + + @Provides + @Singleton + fun provideCredentialManager(@ApplicationContext context: Context): CredentialManager { + return CredentialManager.create(context) + } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt index af4a9491..e640c843 100644 --- a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt +++ b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt @@ -34,8 +34,8 @@ object NetworkModule { fun providesOkHttpClient( loggingInterceptor: HttpLoggingInterceptor, ): OkHttpClient = OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(10, TimeUnit.SECONDS) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(loggingInterceptor) .build() diff --git a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt index f49cf7eb..925dbcea 100644 --- a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt @@ -20,6 +20,10 @@ import com.paw.key.data.repositoryimpl.list.PostsListRepositoryImpl import com.paw.key.data.repositoryimpl.login.AuthRepositoryImpl import com.paw.key.data.repositoryimpl.walklist.WalkListDetailRepositoryImpl import com.paw.key.data.repositoryimpl.walkreview.WalkReviewRepositoryImpl +import com.paw.key.data.remote.datasource.datasourceimpl.AuthRemoteDataSourceImpl +import com.paw.key.data.remote.datasource.datasourceimpl.GoogleAuthDataSourceImpl +import com.paw.key.data.remote.datasource.login.AuthRemoteDataSource +import com.paw.key.data.remote.datasource.login.GoogleAuthDataSource import com.paw.key.domain.repository.ArchivedListRepository import com.paw.key.domain.repository.DummyRepository import com.paw.key.domain.repository.LikeRepository @@ -50,6 +54,18 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) interface RepositoryModule { + @Binds + @Singleton + fun bindAuthRemoteDataSource( + impl: AuthRemoteDataSourceImpl, + ): AuthRemoteDataSource + + @Binds + @Singleton + fun bindGoogleAuthDataSource( + impl: GoogleAuthDataSourceImpl, + ): GoogleAuthDataSource + @Binds fun bindsDummyRepository( dummyRepositoryImpl: DummyRepositoryImpl diff --git a/app/src/main/java/com/paw/key/data/dto/request/LoginRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/LoginRequestDto.kt index 7e780431..f34d4b76 100644 --- a/app/src/main/java/com/paw/key/data/dto/request/LoginRequestDto.kt +++ b/app/src/main/java/com/paw/key/data/dto/request/LoginRequestDto.kt @@ -5,13 +5,13 @@ import kotlinx.serialization.Serializable @Serializable data class LoginRequestDto ( - @SerialName("email") - val email: String, + @SerialName("idToken") + val idToken: String, + @SerialName("deviceId") + val deviceId: String ) -// 테스트용입니다 - - fun LoginRequestDto.toEntity(): LoginRequestDto { - val email = this.email - return LoginRequestDto(email) + val idToken = this.idToken + val deviceId = this.deviceId + return LoginRequestDto(idToken, deviceId) } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/LoginResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/LoginResponseDto.kt index c0b82bd2..89213588 100644 --- a/app/src/main/java/com/paw/key/data/dto/response/LoginResponseDto.kt +++ b/app/src/main/java/com/paw/key/data/dto/response/LoginResponseDto.kt @@ -5,9 +5,8 @@ import kotlinx.serialization.Serializable @Serializable data class LoginResponseDto ( - @SerialName("AccessToken") - val AccessToken: String, - @SerialName("RefreshToken") - val RefreshToken: String + @SerialName("accessToken") + val accessToken: String, + @SerialName("refreshToken") + val refreshToken: String ) -// 테스트용입니다 \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/AuthRemoteDataSourceImpl.kt b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/AuthRemoteDataSourceImpl.kt index ba3bbd12..053f3b68 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/AuthRemoteDataSourceImpl.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/AuthRemoteDataSourceImpl.kt @@ -8,11 +8,14 @@ import com.paw.key.data.service.login.LoginService import javax.inject.Inject class AuthRemoteDataSourceImpl @Inject constructor( - private val loginService: LoginService, + private val loginService: LoginService ) : AuthRemoteDataSource { - override suspend fun login( - providerToken: String, - provider: String, - ): BaseResponse = - loginService.login(providerToken, LoginRequestDto(provider)) -} + override suspend fun login(idToken: String, deviceId: String): LoginResponseDto { + return loginService.login( + LoginRequestDto( + idToken = idToken, + deviceId = deviceId + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/GoogleAuthDataSourceImpl.kt b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/GoogleAuthDataSourceImpl.kt index 7bfa8b0d..d8e23ddb 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/GoogleAuthDataSourceImpl.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/GoogleAuthDataSourceImpl.kt @@ -7,12 +7,8 @@ import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.paw.key.BuildConfig import com.paw.key.core.util.suspendRunCatching -import com.paw.key.data.dto.request.LoginRequestDto -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.LoginResponseDto -import com.paw.key.data.remote.datasource.login.AuthRemoteDataSource import com.paw.key.data.remote.datasource.login.GoogleAuthDataSource -import com.paw.key.data.service.login.LoginService +import timber.log.Timber import javax.inject.Inject class GoogleAuthDataSourceImpl @Inject constructor( @@ -32,5 +28,6 @@ class GoogleAuthDataSourceImpl @Inject constructor( val response = credentialManager.getCredential(context, request) GoogleIdTokenCredential.createFrom(response.credential.data) + } } diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/login/AuthRemoteDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/login/AuthRemoteDataSource.kt index d258304d..3a5849bf 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/login/AuthRemoteDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/login/AuthRemoteDataSource.kt @@ -4,5 +4,5 @@ import com.paw.key.data.dto.response.BaseResponse import com.paw.key.data.dto.response.LoginResponseDto interface AuthRemoteDataSource { - suspend fun login(providerToken: String, provider: String): BaseResponse + suspend fun login(idToken: String, deviceId: String): LoginResponseDto } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/login/AuthRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/login/AuthRepositoryImpl.kt index b0b6a1bb..8cd8095a 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/login/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/login/AuthRepositoryImpl.kt @@ -7,29 +7,33 @@ import com.paw.key.data.dto.response.LoginResponseDto import com.paw.key.data.remote.datasource.login.AuthRemoteDataSource import com.paw.key.data.remote.datasource.login.GoogleAuthDataSource import com.paw.key.domain.repository.login.AuthRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val authRemoteDataSource: AuthRemoteDataSource, private val googleAuthDataSource: GoogleAuthDataSource, - private val context: Context + @ApplicationContext private val context: Context ) : AuthRepository { override suspend fun signInWithGoogle(context: Context): Result = googleAuthDataSource.signIn(context).map { it.idToken } - override suspend fun login(providerToken: String, provider: String): Result = + override suspend fun login(idToken: String, deviceId: String): Result = suspendRunCatching { - val loginResponse = authRemoteDataSource.login(providerToken, provider).data + + val loginResponse = authRemoteDataSource.login(idToken, deviceId) UserDataStore.saveAcessToken( - context = context, - token = loginResponse.AccessToken + context = this.context, + token = loginResponse.accessToken ) UserDataStore.saveRefreshToken( - context = context, - token = loginResponse.RefreshToken + context = this.context, + token = loginResponse.refreshToken ) + loginResponse } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/login/LoginService.kt b/app/src/main/java/com/paw/key/data/service/login/LoginService.kt index 020c63ab..f6e953d4 100644 --- a/app/src/main/java/com/paw/key/data/service/login/LoginService.kt +++ b/app/src/main/java/com/paw/key/data/service/login/LoginService.kt @@ -9,9 +9,8 @@ import retrofit2.http.POST interface LoginService { - @POST("api/v1/auth/login") + @POST("auth/google/login") suspend fun login( - @Header("Authorization") providerToken: String, @Body loginRequestDto: LoginRequestDto - ): BaseResponse + ): LoginResponseDto } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt b/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt index c0d7079a..6791bf6f 100644 --- a/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt +++ b/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt @@ -5,5 +5,5 @@ import com.paw.key.data.dto.response.LoginResponseDto interface AuthRepository { suspend fun signInWithGoogle(context: Context): Result - suspend fun login(providerToken: String, provider: String): Result + suspend fun login(idToken: String, deviceId: String): Result } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt index a4576ec4..ec8f8c10 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt @@ -1,49 +1,44 @@ package com.paw.key.presentation.ui.login import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues 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.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.relocation.BringIntoViewRequester -import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.R -import com.paw.key.core.designsystem.component.PawkeyButton -import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.util.PreferenceDataStore -import com.paw.key.core.extension.noRippleClickable -import com.paw.key.presentation.ui.login.component.LoginTextField +import com.paw.key.presentation.ui.login.component.LoginSocialButton import com.paw.key.presentation.ui.login.viewmodel.LoginViewModel import kotlinx.coroutines.launch @@ -55,7 +50,7 @@ fun LoginRoute( navigateHome: () -> Unit, snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, - viewModel: LoginViewModel = hiltViewModel() + viewModel: LoginViewModel = hiltViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() val isLoginFormValid = viewModel.state.collectAsStateWithLifecycle().value.isLoginValid @@ -93,7 +88,7 @@ fun LoginScreen( paddingValues: PaddingValues, navigateUp: () -> Unit, navigateNext: () -> Unit, - navigateHome : () -> Unit, + navigateHome: () -> Unit, onEmailChanged: (String) -> Unit, onPasswordChanged: (String) -> Unit, onClickIcon: () -> Unit, @@ -103,131 +98,100 @@ fun LoginScreen( isPasswordVisible: Boolean, isLoginFormValid: Boolean, modifier: Modifier = Modifier, + viewModel: LoginViewModel = hiltViewModel(), ) { - val bringIntoViewRequester = remember { BringIntoViewRequester() } - val focusRequester = remember { FocusRequester() } - val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current val scrollState = rememberScrollState() - val passwordFocusRequester = remember { FocusRequester() } - Column( + Box( modifier = modifier .fillMaxSize() - .statusBarsPadding() .background(PawKeyTheme.colors.white1) - .verticalScroll(scrollState) ) { - TopBar( - title = "기존 계정으로 로그인", - onBackClick = navigateUp, - onClickTitle = navigateHome, - modifier = Modifier.padding( - top = paddingValues.calculateTopPadding() - ) - ) - Column( modifier = Modifier - .padding(top = 80.dp) - .padding(horizontal = 16.dp) - .imePadding() + .matchParentSize() + .statusBarsPadding() + .background(PawKeyTheme.colors.white1) + .verticalScroll(scrollState) + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - Spacer(modifier = Modifier.height(60.dp)) - Text( - text = "아이디", - modifier = Modifier.padding(bottom = 8.dp), - color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.body14Sb - ) + Spacer(modifier = Modifier.height(130.dp)) - LoginTextField( - textValue = email, - placeHolder = "사용하실 아이디를 입력해주세요", - isPassword = true, - onTextChanged = onEmailChanged, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions( - onNext = { - passwordFocusRequester.requestFocus() - } - ) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_login_title_logo), + contentDescription = stringResource(id = R.string.ic_login_main_logo), + tint = PawKeyTheme.colors.primary, + modifier = Modifier.padding(start = 19.dp) ) - Spacer(modifier = Modifier.height(40.dp)) - Text( - text = "비밀번호", - modifier = Modifier.padding(bottom = 8.dp), - color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.body14Sb + text = stringResource(R.string.ic_login_main_text), + color = PawKeyTheme.colors.contents, + style = PawKeyTheme.typography.header3, + modifier = Modifier.padding(start = 19.dp) ) - LoginTextField( - textValue = password, - placeHolder = "사용하실 비밀번호를 입력해주세요", - isPassword = isPasswordVisible, - onTextChanged = onPasswordChanged, - focusRequester = passwordFocusRequester, - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), - suffix = { - Icon( - imageVector = ImageVector.vectorResource( - if (isPasswordVisible) R.drawable.ic_eye_linear_gray_valid - else R.drawable.ic_eye_linear_invalid - ), - contentDescription = null, - modifier = Modifier.noRippleClickable(onClickIcon), - tint = PawKeyTheme.colors.gray200 - ) - }, - modifier = Modifier - .bringIntoViewRequester(bringIntoViewRequester) - .focusRequester(focusRequester) - .onFocusChanged { focusState -> - if (focusState.isFocused) { - coroutineScope.launch { - bringIntoViewRequester.bringIntoView() - } - } + Spacer(modifier = Modifier.height(310.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(R.drawable.img_login_sub), + contentDescription = stringResource(R.string.ic_login_sub_image), + ) + + LoginSocialButton( + logo = R.drawable.ic_login_kakao, + loginText = stringResource(R.string.ic_login_kakao), + onClick = {}, + modifier = Modifier + .background( + shape = RoundedCornerShape(12.dp), + color = Color(0xFFFEE500) + ) + ) + + LoginSocialButton( + logo = R.drawable.ic_login_google, + loginText = stringResource(R.string.ic_login_google), + onClick = { + viewModel.onGoogleSignIn( + context = context, + onSuccess = navigateHome + ) }, - ) - } + modifier = Modifier + .background( + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF2F2F2) + ) + ) + } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(34.dp)) + } - Column( + Image( + painter = painterResource(R.drawable.img_login_main), + contentDescription = stringResource(R.string.ic_login_main_image), modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 24.dp) - .navigationBarsPadding() - .padding(bottom = 60.dp) - ) { - /* PawkeyButton( - text = "신규 계정으로 회원가입", - onClick = navigateUp, - enabled = true, - isBackGround = true, - isBorder = false, - modifier = Modifier.fillMaxWidth() - )*/ - - Spacer(modifier = Modifier.height(12.dp)) - - PawkeyButton( - text = "로그인", - onClick = navigateNext, - enabled = isLoginFormValid, - modifier = Modifier.fillMaxWidth() - ) - } + .size(370.dp) + .align(Alignment.CenterEnd) + .offset(x = 70.dp), + ) } } + @Preview(showBackground = true) @Composable -private fun PreviewLoginScreen(){ - PawKeyTheme{ +private fun PreviewLoginScreen() { + PawKeyTheme { LoginScreen( paddingValues = PaddingValues(), navigateUp = {}, diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/component/LoginSocialButton.kt b/app/src/main/java/com/paw/key/presentation/ui/login/component/LoginSocialButton.kt new file mode 100644 index 00000000..17500305 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/login/component/LoginSocialButton.kt @@ -0,0 +1,69 @@ +package com.paw.key.presentation.ui.login.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.paw.key.R +import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.extension.noRippleClickable + +@Preview(showBackground = true) +@Composable +private fun PreviewLoginSocialButton() { + PawKeyTheme { + LoginSocialButton( + logo = R.drawable.ic_login_kakao, + loginText = "Login with Google", + onClick = {} + ) + } +} + +@Composable +fun LoginSocialButton( + logo: Int, + loginText: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxWidth() + .background( + color = Color.Transparent, + shape = RoundedCornerShape(12.dp) + + ) + .noRippleClickable { onClick() } + .padding(vertical = 12.dp, horizontal = 16.dp) + ) { + Icon( + imageVector = ImageVector.vectorResource(id = logo), + contentDescription = stringResource(id = R.string.ic_login_button_content), + tint = Color.Unspecified, + modifier = Modifier + .align(alignment = Alignment.CenterStart) + ) + + Text( + text = loginText, + color = PawKeyTheme.colors.contents, + style = PawKeyTheme.typography.body14Sb, + modifier = Modifier + .align(alignment = Alignment.Center) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt index 4de58106..b1959ab6 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt @@ -1,14 +1,21 @@ package com.paw.key.presentation.ui.login.viewmodel +import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.login.AuthRepository import com.paw.key.presentation.ui.login.state.LoginSideEffect import com.paw.key.presentation.ui.login.state.LoginState +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject +@HiltViewModel class LoginViewModel @Inject constructor( private val authRepository: AuthRepository, ) : ViewModel() { @@ -20,23 +27,45 @@ class LoginViewModel @Inject constructor( val sideEffect: StateFlow get() = _sideEffect.asStateFlow() - fun onEmailChanged(email: String) { - _state.value = _state.value.copy( - email = email - ) + fun onGoogleSignIn( + context: Context, + onSuccess: () -> Unit, + ) { + viewModelScope.launch { + authRepository.signInWithGoogle(context) + .onSuccess { idToken -> + val deviceId = getDeviceId(context) + + authRepository.login(idToken, deviceId) + .onSuccess { response -> + onSuccess() + } + .onFailure { e -> + Timber.e(e, "Backend login failed") + } + } + .onFailure { e -> + Timber.e(e, "Google sign-in failed") + } + } } - fun onPasswordChanged(password: String) { - _state.value = _state.value.copy( - password = password + private fun getDeviceId(context: Context): String { + return android.provider.Settings.Secure.getString( + context.contentResolver, + android.provider.Settings.Secure.ANDROID_ID ) } - fun onPasswordVisibilityChanged() { - _state.value = _state.value.copy( - isPasswordVisible = !_state.value.isPasswordVisible - ) + fun onEmailChanged(email: String) { + _state.update { it.copy(email = email) } } + fun onPasswordChanged(password: String) { + _state.update { it.copy(password = password) } + } + fun onPasswordVisibilityChanged() { + _state.update { it.copy(isPasswordVisible = !it.isPasswordVisible) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt b/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt index 1b9ec1db..efd65292 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt @@ -277,7 +277,7 @@ fun PawKeyNavHost( onboardingNavGraph( paddingValues = paddingValues, navigateUp = navigator::navigateUp, - navigateNext = navigator::navigateSignUp, + navigateNext = navigator::navigateLogin, navigateSignUp = navigator::navigateLogin, snackBarHostState = snackbarHostState ) diff --git a/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt index f3627097..92972678 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt @@ -2,20 +2,25 @@ package com.paw.key.presentation.ui.onboard import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +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.material3.Icon import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.paw.key.R -import com.paw.key.core.designsystem.component.PawkeyButton +import com.paw.key.core.designsystem.component.DogkyButton import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.onboard.component.OnboardPager import com.paw.key.presentation.ui.onboard.component.OnboardingPosting @@ -44,8 +49,6 @@ fun OnboardingRoute( snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, ) { - - OnboardingScreen( paddingValues = paddingValues, navigateUp = navigateUp, @@ -65,61 +68,57 @@ fun OnboardingScreen( snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, ) { - - val jobList = listOf( - OnboardingPosting( - title = "우리의 강아지를 위한 산책,\nPAWKEY와 함께해요!", // 원본 텍스트로 변경 - subtitle = "", - backImg = R.drawable.onboard1 - ), - OnboardingPosting( - title = "새로운 산책의 시작", - subtitle = "최적화된 산책 환경에서 더 즐겁고 의미있는 산책을\n우리 강아지와 함께 시작해보세요.", - backImg = R.drawable.onboard2 - ), - OnboardingPosting( - title = "매일매일 새로운 루트로!\n우리 강아지와 나만의 산책.", - subtitle = "", - backImg = R.drawable.onboard3 - ), - OnboardingPosting( - title = "산책을 기록하고 공유하고\n수정해보세요.", - subtitle = "", - backImg = R.drawable.onboard4 - ) - ) - - Box( + Column( modifier = modifier .fillMaxSize() - .background(color = PawKeyTheme.colors.green500) + .background(color = PawKeyTheme.colors.white1), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally ) { + Spacer(modifier = Modifier.height(40.dp)) + + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_onboard_main_logo), + contentDescription = stringResource(id = R.string.ic_onboarding_top_icon), + tint = Color.Unspecified + ) + + Spacer(modifier = Modifier.height(13.dp)) + OnboardPager( - jobList = jobList + jobList = listOf( + OnboardingPosting( + title = stringResource(id = R.string.ic_onboadring_pager_title1), + subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext1), + backImg = R.drawable.img_onboarding_1 + ), + OnboardingPosting( + title = stringResource(id = R.string.ic_onboadring_pager_title2), + subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext2), + backImg = R.drawable.img_onboarding_2 + ), + OnboardingPosting( + title = stringResource(id = R.string.ic_onboadring_pager_title3), + subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext3), + backImg = R.drawable.img_onboarding_2 + ), + OnboardingPosting( + title = stringResource(id = R.string.ic_onboadring_pager_title4), + subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext4), + backImg = R.drawable.img_onboarding_2 + ), + ) ) - Column( - verticalArrangement = Arrangement.spacedBy(12.dp), + DogkyButton( + text = stringResource(id = R.string.ic_onboarding_button), + enabled = true, + onClick = navigateNext, modifier = Modifier - .align(Alignment.BottomCenter) - .padding(horizontal = 16.dp, vertical = 64.dp) - .fillMaxWidth() - ) { - PawkeyButton( - text = "신규 계정으로 회원가입", - enabled = true, - onClick = navigateNext, // Todo : 나중에 로그인, 회원가입 네이밍 수정 - isBackGround = true, - isBorder = false - ) + .padding(horizontal = 16.dp) + ) - PawkeyButton( - text = "기존 계정으로 로그인", - enabled = true, - isBorder = false, - onClick = { navigateSignUp() }, - ) - } + Spacer(modifier = Modifier.height(34.dp)) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt b/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt index 6d10c961..1ba52997 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt @@ -1,19 +1,16 @@ package com.paw.key.presentation.ui.onboard.component import androidx.compose.animation.Crossfade -import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box 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.width +import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -21,22 +18,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.SpanStyle -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.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.zIndex import com.paw.key.R import com.paw.key.core.designsystem.component.PageIndicator import com.paw.key.core.designsystem.theme.PawKeyTheme @@ -48,12 +34,11 @@ private fun PreviewOnboardPager() { OnboardPager( jobList = listOf( OnboardingPosting( - title = "우리의 강아지를 위한 산책,\nPAWKEY와 함께해요!", // 원본 텍스트로 변경 - subtitle = "", - backImg = R.drawable.onboard1 + title = "우리 강아지를 위한 산책", + subtitle = "DOGKY와 즐거운 산책을 시작해봐요!", + backImg = R.drawable.img_onboarding_1 ), ) - ) } } @@ -73,11 +58,7 @@ fun OnboardPager( modifier = modifier .fillMaxWidth() .height(LocalConfiguration.current.screenHeightDp.dp * 0.7f) - .background( - color = PawKeyTheme.colors.white1, - shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp) - ) - .clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp)) + .background(color = PawKeyTheme.colors.white1) ) { HorizontalPager( state = pagerState, @@ -88,44 +69,13 @@ fun OnboardPager( Column( modifier = Modifier - .align(Alignment.TopStart) - .padding(top = 80.dp, start = 24.dp, end = 24.dp) - .zIndex(2f) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { Crossfade(targetState = currentItem?.title) { title -> title?.let { - val annotated = buildAnnotatedString { - when (currentPage) { - 0 -> { - val start = it.indexOf("PAWKEY") - val end = start + "PAWKEY".length - if (start != -1) { - append(it.substring(0, start)) - withStyle(SpanStyle(color = PawKeyTheme.colors.green500)) { - append("PAWKEY") - } - append(it.substring(end)) - } else { - append(it) - } - } - - 1 -> { - withStyle(SpanStyle(color = PawKeyTheme.colors.green500)) { - append(it) - } - } - - else -> { - withStyle(SpanStyle(color = PawKeyTheme.colors.green500)) { - append(it) - } - } - } - } - Text( - text = annotated, + text = it, style = PawKeyTheme.typography.head24B.copy(lineHeight = 36.sp), color = PawKeyTheme.colors.black, ) @@ -146,8 +96,8 @@ fun OnboardPager( PageIndicator( numberOfPages = pageCount, selectedPage = currentPage, - selectedColor = PawKeyTheme.colors.green500, - defaultColor = PawKeyTheme.colors.green200, + selectedColor = PawKeyTheme.colors.primary, + defaultColor = PawKeyTheme.colors.gray100, modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = 20.dp) @@ -166,8 +116,9 @@ fun OnboardingListItem(backImg: Int) { Image( painter = painterResource(id = backImg), contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.FillBounds + modifier = Modifier + .align(alignment = Alignment.Center) + .size(360.dp), ) } } diff --git a/app/src/main/res/drawable/btn_login_google.png b/app/src/main/res/drawable/btn_login_google.png new file mode 100644 index 00000000..a6663249 Binary files /dev/null and b/app/src/main/res/drawable/btn_login_google.png differ diff --git a/app/src/main/res/drawable/btn_login_kakao.png b/app/src/main/res/drawable/btn_login_kakao.png new file mode 100644 index 00000000..e82a67e4 Binary files /dev/null and b/app/src/main/res/drawable/btn_login_kakao.png differ diff --git a/app/src/main/res/drawable/ic_login_google.xml b/app/src/main/res/drawable/ic_login_google.xml new file mode 100644 index 00000000..c34e2c50 --- /dev/null +++ b/app/src/main/res/drawable/ic_login_google.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_login_kakao.xml b/app/src/main/res/drawable/ic_login_kakao.xml new file mode 100644 index 00000000..19100fc8 --- /dev/null +++ b/app/src/main/res/drawable/ic_login_kakao.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_login_sub_text.xml b/app/src/main/res/drawable/ic_login_sub_text.xml new file mode 100644 index 00000000..164c2e86 --- /dev/null +++ b/app/src/main/res/drawable/ic_login_sub_text.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_login_title_logo.xml b/app/src/main/res/drawable/ic_login_title_logo.xml new file mode 100644 index 00000000..854bbd42 --- /dev/null +++ b/app/src/main/res/drawable/ic_login_title_logo.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboard_main_logo.xml b/app/src/main/res/drawable/ic_onboard_main_logo.xml new file mode 100644 index 00000000..e8b28ee1 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboard_main_logo.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/img_login_main.png b/app/src/main/res/drawable/img_login_main.png new file mode 100644 index 00000000..50f70953 Binary files /dev/null and b/app/src/main/res/drawable/img_login_main.png differ diff --git a/app/src/main/res/drawable/img_login_sub.png b/app/src/main/res/drawable/img_login_sub.png new file mode 100644 index 00000000..e6f914d1 Binary files /dev/null and b/app/src/main/res/drawable/img_login_sub.png differ diff --git a/app/src/main/res/drawable/img_login_sub_text.png b/app/src/main/res/drawable/img_login_sub_text.png new file mode 100644 index 00000000..55fe02f8 Binary files /dev/null and b/app/src/main/res/drawable/img_login_sub_text.png differ diff --git a/app/src/main/res/drawable/img_onboarding_1.png b/app/src/main/res/drawable/img_onboarding_1.png new file mode 100644 index 00000000..150934bb Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_1.png differ diff --git a/app/src/main/res/drawable/img_onboarding_2.png b/app/src/main/res/drawable/img_onboarding_2.png new file mode 100644 index 00000000..f787bbba Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_2.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f9d1dff..93b322c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,9 +14,29 @@ 신규계정으로 회원가입 기존 계정으로 회원가입 + 도키와 도키도키한\n산책을 시작해요! + 카카오톡으로 계속하기 + 구글로 계속하기 + main logo + main image + sub image + login button icon 최근 산책 + 우리 강아지를 위한 산책 + 산책을 경험에 맞춰 기록하세요 + 기록한 산책길을 이웃과 나누세요 + 공유된 산책길을 따라 걸어보세요 + + DOGKY와 즐거운 산책을 시작해봐요! + 산책의 거리, 분위기, 활동 등을 카테고리에 따라\n특별한 일상으로 저장할 수 있어요 + 내가 걸었던 루트를 공유하고\n다른 보호자들과 함께 즐길 수 있어요 + 이웃들이 남긴 루프를 걸으며\n해당 루트에 대한 유용한 정보를 얻을 수 있어요 + + main logo + Next + 회원가입 다음으로 견주님에 대해 알려주세요