diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4ff3eb0e..eacc6bf6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,7 @@ android { buildConfigField("String", "KAKAO_REST_API_KEY", properties["kakao.rest.api"].toString()) buildConfigField("String", "NAVERMAP_CLIENT_SECRET", properties["NAVERMAP_CLIENT_SECRET"].toString()) buildConfigField("String", "NAVERMAP_CLIENT_ID", properties["NAVERMAP_CLIENT_ID"].toString()) + buildConfigField("String","GOOGLE_WEB_CLIENT_ID",properties["google.client.id"].toString()) manifestPlaceholders["KAKAO_NATIVE_KEY"] = properties["kakao.native.key"].toString() } @@ -46,6 +47,7 @@ android { compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true } kotlinOptions { jvmTarget = "11" @@ -108,4 +110,13 @@ dependencies { // 네이버 implementation(libs.bundles.naverMaps) + + //구글 + implementation(libs.androidx.credentials) + implementation(libs.googleid) + implementation(libs.androidx.credentials.play.services.auth) + coreLibraryDesugaring(libs.desugar.jdk.libs) + + // 암호화 + implementation(libs.androidx.security) } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt b/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt index 9736d201..a68fa864 100644 --- a/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt +++ b/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -44,11 +43,10 @@ import androidx.compose.ui.zIndex import coil.compose.AsyncImage import coil.request.ImageRequest import com.paw.key.R -import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.designsystem.theme.Gray100 -import com.paw.key.core.util.noRippleClickable +import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.extension.noRippleClickable import com.paw.key.domain.model.entity.walklist.CategoryTop3Entity -import kotlinx.serialization.json.JsonNull.content @OptIn(ExperimentalLayoutApi::class) @Composable diff --git a/app/src/main/java/com/paw/key/core/util/PreferenceDataStore.kt b/app/src/main/java/com/paw/key/core/util/PreferenceDataStore.kt index 843e3b9f..3a16757a 100644 --- a/app/src/main/java/com/paw/key/core/util/PreferenceDataStore.kt +++ b/app/src/main/java/com/paw/key/core/util/PreferenceDataStore.kt @@ -1,12 +1,15 @@ package com.paw.key.core.util import android.content.Context +import android.content.SharedPreferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey import com.naver.maps.geometry.LatLng import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -46,7 +49,6 @@ object PreferenceDataStore { private val summaryStore get() = appContext.summaryStore - // 기존 함수들... suspend fun saveWalkSummary( points: List, totalDistance: Float, @@ -117,7 +119,7 @@ object PreferenceDataStore { userId: Int, userName: String, petId: Int, - petName: String + petName: String, ) { summaryStore.edit { it[USER_ID_KEY] = userId @@ -147,7 +149,7 @@ object PreferenceDataStore { val userId: Int, val userName: String, val petId: Int, - val petName: String + val petName: String, ) fun getUserInfo(): Flow = summaryStore.data.map { @@ -177,7 +179,7 @@ object PreferenceDataStore { guId: Int, dongId: Int, guName: String, - dongName: String + dongName: String, ) { summaryStore.edit { preferences -> preferences[SELECTED_GU_ID_KEY] = guId @@ -246,7 +248,7 @@ object PreferenceDataStore { val dongId: Int, val guName: String, val dongName: String, - val activeRegion: String + val activeRegion: String, ) { val displayLocation: String get() = if (guName.isNotEmpty() && dongName.isNotEmpty()) { @@ -286,8 +288,61 @@ object PreferenceDataStore { preferences.remove(ACTIVE_REGION_KEY) } } +} + +object UserDataStore { + private val ACCESS_TOKEN = "ACCESS_TOKEN" + private val REFRESH_TOKEN = "REFRESH_TOKEN" + private val PREFERENCES_NAME = "user_preferences" + + private fun getSharedPreferences(context: Context): SharedPreferences { + val masterKey = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + return EncryptedSharedPreferences.create( + context, + PREFERENCES_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + fun saveAcessToken(context: Context, token: String) { + val sharedPreferences = getSharedPreferences(context) + with(sharedPreferences.edit()) { + putString(ACCESS_TOKEN, token) + commit() + } + } + + fun saveRefreshToken(context: Context, token: String) { + val sharedPreferences = getSharedPreferences(context) + with(sharedPreferences.edit()) { + putString(REFRESH_TOKEN, token) + commit() + } + } - suspend fun clearAllData() { - summaryStore.edit { it.clear() } + fun getAccessToken(context: Context): String { + val sharedPreferences = getSharedPreferences(context) + return sharedPreferences.getString(ACCESS_TOKEN, "") ?: "" } -} \ No newline at end of file + + fun getRefreshToken(context: Context): String { + val sharedPreferences = getSharedPreferences(context) + return sharedPreferences.getString(REFRESH_TOKEN, "") ?: "" + } + + fun removeToken(context: Context) { + val sharedPreferences = getSharedPreferences(context) + with(sharedPreferences.edit()) { + remove(ACCESS_TOKEN) + remove(REFRESH_TOKEN) + commit() + } + } +} + + diff --git a/app/src/main/java/com/paw/key/core/util/suspendRunCating.kt b/app/src/main/java/com/paw/key/core/util/suspendRunCating.kt new file mode 100644 index 00000000..8e149871 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/util/suspendRunCating.kt @@ -0,0 +1,19 @@ +package com.paw.key.core.util + +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.ensureActive +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.coroutineContext + +suspend fun suspendRunCatching(block: suspend () -> R): Result { + return try { + Result.success(block()) + } catch (t: TimeoutCancellationException) { + Result.failure(t) + } catch (c: CancellationException) { + throw c + } catch (e: Throwable) { + coroutineContext.ensureActive() + Result.failure(e) + } +} \ No newline at end of file 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 42735e1f..f49cf7eb 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 @@ -17,6 +17,7 @@ import com.paw.key.data.repositoryimpl.sharedwalk.SharedWalkRepositoryImpl import com.paw.key.data.repositoryimpl.home.HomeRegionRepositoryImpl import com.paw.key.data.repositoryimpl.home.RegionCurrentRepositoryImpl 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.domain.repository.ArchivedListRepository @@ -33,6 +34,7 @@ import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository import com.paw.key.domain.repository.home.HomeRegionRepository import com.paw.key.domain.repository.home.RegionCurrentRepository import com.paw.key.domain.repository.list.PostsListRepository +import com.paw.key.domain.repository.login.AuthRepository import com.paw.key.domain.repository.petprofile.PetProfileRepository import com.paw.key.domain.repository.userprofile.UserProfileRepository import com.paw.key.domain.repository.walkcourse.WalkCourseRepository @@ -165,4 +167,10 @@ interface RepositoryModule { fun bindRegionCurrentRepository( impl: RegionCurrentRepositoryImpl ) : RegionCurrentRepository + + @Binds + @Singleton + fun bindLoginRepository( + impl: AuthRepositoryImpl + ) : AuthRepository } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt index eecd92db..8d5f9f23 100644 --- a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt +++ b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt @@ -4,17 +4,17 @@ import com.paw.key.data.service.ArchivedListService import com.paw.key.data.service.DummyService import com.paw.key.data.service.LikeService import com.paw.key.data.service.PetProfileService -import com.paw.key.data.service.onboarding.OnboardingInfoService -import com.paw.key.data.service.onboarding.OnboardingPetsService -import com.paw.key.data.service.onboarding.OnboardingRegionService import com.paw.key.data.service.RegionService import com.paw.key.data.service.SavedListService import com.paw.key.data.service.UserProfileService import com.paw.key.data.service.filter.FilterOptionService -import com.paw.key.data.service.sharedwalk.SharedWalkService import com.paw.key.data.service.home.HomeRegionService -import com.paw.key.data.service.home.RegionCurrentService import com.paw.key.data.service.list.PostsListService +import com.paw.key.data.service.login.LoginService +import com.paw.key.data.service.onboarding.OnboardingInfoService +import com.paw.key.data.service.onboarding.OnboardingPetsService +import com.paw.key.data.service.onboarding.OnboardingRegionService +import com.paw.key.data.service.sharedwalk.SharedWalkService import com.paw.key.data.service.walkcourse.WalkCourseService import com.paw.key.data.service.walklist.WalkListDetailService import com.paw.key.data.service.walkreview.WalkReviewService @@ -68,7 +68,7 @@ object ServiceModule { @Provides @Singleton fun provideHomeRegionService(retrofit: Retrofit): HomeRegionService = - retrofit.create(HomeRegionService::class.java) + retrofit.create() //마이페이지 @Provides @@ -119,6 +119,6 @@ object ServiceModule { @Provides @Singleton - fun provideRegionCurrentService(retrofit: Retrofit): RegionCurrentService = + fun provideLoginService(retrofit: Retrofit): LoginService = retrofit.create() } \ No newline at end of file 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 new file mode 100644 index 00000000..7e780431 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/LoginRequestDto.kt @@ -0,0 +1,17 @@ +package com.paw.key.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequestDto ( + @SerialName("email") + val email: String, +) +// 테스트용입니다 + + +fun LoginRequestDto.toEntity(): LoginRequestDto { + val email = this.email + return LoginRequestDto(email) +} \ 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 new file mode 100644 index 00000000..c0b82bd2 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/LoginResponseDto.kt @@ -0,0 +1,13 @@ +package com.paw.key.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginResponseDto ( + @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 new file mode 100644 index 00000000..ba3bbd12 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/AuthRemoteDataSourceImpl.kt @@ -0,0 +1,18 @@ +package com.paw.key.data.remote.datasource.datasourceimpl + +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.service.login.LoginService +import javax.inject.Inject + +class AuthRemoteDataSourceImpl @Inject constructor( + private val loginService: LoginService, +) : AuthRemoteDataSource { + override suspend fun login( + providerToken: String, + provider: String, + ): BaseResponse = + loginService.login(providerToken, LoginRequestDto(provider)) +} 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 new file mode 100644 index 00000000..7bfa8b0d --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/GoogleAuthDataSourceImpl.kt @@ -0,0 +1,36 @@ +package com.paw.key.data.remote.datasource.datasourceimpl + +import android.content.Context +import androidx.credentials.CredentialManager +import androidx.credentials.GetCredentialRequest +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 javax.inject.Inject + +class GoogleAuthDataSourceImpl @Inject constructor( + private val credentialManager: CredentialManager, +) : GoogleAuthDataSource { + override suspend fun signIn(context: Context): Result = + suspendRunCatching { + val googleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setAutoSelectEnabled(false) + .setServerClientId(BuildConfig.GOOGLE_WEB_CLIENT_ID) + .build() + + val request = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + val response = credentialManager.getCredential(context, request) + GoogleIdTokenCredential.createFrom(response.credential.data) + } +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/home/RegionCurrentDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/home/RegionCurrentDataSource.kt index 573f7408..a07b9a3f 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/home/RegionCurrentDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/home/RegionCurrentDataSource.kt @@ -1,14 +1,12 @@ package com.paw.key.data.remote.datasource.home -import com.paw.key.data.dto.request.home.HomeRegionRequest import com.paw.key.data.service.home.HomeRegionService -import com.paw.key.data.service.home.RegionCurrentService import javax.inject.Inject class RegionCurrentDataSource @Inject constructor( - private val service: RegionCurrentService + private val service: HomeRegionService ) { - suspend fun RegionCurrent(userId: Int) = - service.RegionCurrent(userId) + suspend fun regionCurrent(userId: Int) = + service.regionCurrent(userId) } 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 new file mode 100644 index 00000000..d258304d --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/login/AuthRemoteDataSource.kt @@ -0,0 +1,8 @@ +package com.paw.key.data.remote.datasource.login + +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 +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/login/GoogleAuthDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/login/GoogleAuthDataSource.kt new file mode 100644 index 00000000..7cd8c8d2 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/login/GoogleAuthDataSource.kt @@ -0,0 +1,10 @@ +package com.paw.key.data.remote.datasource.login + +import android.content.Context +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.LoginResponseDto + +interface GoogleAuthDataSource { + suspend fun signIn(context: Context): Result +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/home/RegionCurrentRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/home/RegionCurrentRepositoryImpl.kt index 81035d5a..451e61a6 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/home/RegionCurrentRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/home/RegionCurrentRepositoryImpl.kt @@ -9,12 +9,11 @@ class RegionCurrentRepositoryImpl @Inject constructor( private val dataSource: RegionCurrentDataSource, ) : RegionCurrentRepository { - override suspend fun RegionCurrent(userId: Int): Result { + override suspend fun regionCurrent(userId: Int): Result { return runCatching { - val response = dataSource.RegionCurrent(userId) + val response = dataSource.regionCurrent(userId) if (response.code == "S000") { - // 올바른 타입 반환 (RegionCurrentDataEntity) - response.data.toEntity() // DTO에서 Entity로 변환 + response.data.toEntity() } else { throw Exception(response.message) } 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 new file mode 100644 index 00000000..b0b6a1bb --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/login/AuthRepositoryImpl.kt @@ -0,0 +1,35 @@ +package com.paw.key.data.repositoryimpl.login + +import android.content.Context +import com.paw.key.core.util.UserDataStore +import com.paw.key.core.util.suspendRunCatching +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 javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val authRemoteDataSource: AuthRemoteDataSource, + private val googleAuthDataSource: GoogleAuthDataSource, + 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 = + suspendRunCatching { + val loginResponse = authRemoteDataSource.login(providerToken, provider).data + + UserDataStore.saveAcessToken( + context = context, + token = loginResponse.AccessToken + ) + UserDataStore.saveRefreshToken( + context = context, + token = loginResponse.RefreshToken + ) + loginResponse + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt b/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt index b53ec1fc..4276b144 100644 --- a/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt +++ b/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt @@ -1,8 +1,11 @@ package com.paw.key.data.service.home import com.paw.key.data.dto.request.home.HomeRegionRequest +import com.paw.key.data.dto.response.BaseResponse import com.paw.key.data.dto.response.home.HomeRegionResponse +import com.paw.key.data.dto.response.home.RegionCurrentResponseDto import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.PATCH @@ -14,4 +17,8 @@ interface HomeRegionService { @Body request: HomeRegionRequest, ): HomeRegionResponse + @GET("regions/current") + suspend fun regionCurrent( + @Header("X-USER-ID") userId: Int + ): BaseResponse } diff --git a/app/src/main/java/com/paw/key/data/service/home/RegionCurrentService.kt b/app/src/main/java/com/paw/key/data/service/home/RegionCurrentService.kt deleted file mode 100644 index a76e8294..00000000 --- a/app/src/main/java/com/paw/key/data/service/home/RegionCurrentService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.paw.key.data.service.home - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.home.RegionCurrentResponseDto -import retrofit2.http.GET -import retrofit2.http.Header - -interface RegionCurrentService { - @GET("regions/current") - suspend fun RegionCurrent( - @Header("X-USER-ID") userId: Int - ):BaseResponse -} \ 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 new file mode 100644 index 00000000..020c63ab --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/login/LoginService.kt @@ -0,0 +1,17 @@ +package com.paw.key.data.service.login + +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 retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.POST + + +interface LoginService { + @POST("api/v1/auth/login") + suspend fun login( + @Header("Authorization") providerToken: String, + @Body loginRequestDto: LoginRequestDto + ): BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/model/entity/login/LoginModel.kt b/app/src/main/java/com/paw/key/domain/model/entity/login/LoginModel.kt new file mode 100644 index 00000000..90f23c22 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/model/entity/login/LoginModel.kt @@ -0,0 +1,6 @@ +package com.paw.key.domain.model.entity.login + +data class LoginModel ( + val AccessToken: String, + val RefreshToken: String +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/home/RegionCurrentRepository.kt b/app/src/main/java/com/paw/key/domain/repository/home/RegionCurrentRepository.kt index db5c32d2..7c999e84 100644 --- a/app/src/main/java/com/paw/key/domain/repository/home/RegionCurrentRepository.kt +++ b/app/src/main/java/com/paw/key/domain/repository/home/RegionCurrentRepository.kt @@ -1,8 +1,7 @@ package com.paw.key.domain.repository.home -import com.paw.key.domain.model.entity.home.HomeRegionDataEntity import com.paw.key.domain.model.entity.home.RegionCurrentDataEntity interface RegionCurrentRepository { - suspend fun RegionCurrent(userId: Int): Result + suspend fun regionCurrent(userId: Int): Result } \ 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 new file mode 100644 index 00000000..c0d7079a --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt @@ -0,0 +1,9 @@ +package com.paw.key.domain.repository.login + +import android.content.Context +import com.paw.key.data.dto.response.LoginResponseDto + +interface AuthRepository { + suspend fun signInWithGoogle(context: Context): Result + suspend fun login(providerToken: String, provider: String): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt index 291c0202..7510e675 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt @@ -40,7 +40,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.R import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.core.util.noRippleClickable +import com.paw.key.core.extension.noRippleClickable import com.paw.key.presentation.ui.course.entire.tab.map.List.state.TapListContract import com.paw.key.presentation.ui.course.entire.tab.map.List.viewmodel.TapListViewModel diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt index fe81fe91..f7734221 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt @@ -95,7 +95,6 @@ fun HomeScreen( val view = LocalView.current val window = (view.context as? Activity)?.window val postsResult = state.postsResult - val posts = postsResult?.posts SideEffect { window?.let { diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt index 717a8f24..df2cbca6 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt @@ -38,6 +38,7 @@ private fun PreviewTrackingCard() { @Composable fun TrackingCard( onClick: () -> Unit, + modifier: Modifier = Modifier ) { Box( contentAlignment = Alignment.Center, diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt b/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt index 7a3d8953..b2f934de 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt @@ -56,9 +56,6 @@ class HomeContract { } } } - - val isLocationSelected: Boolean - get() = selectedGuId != 0 && selectedDongId != 0 } @Immutable diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt index 17371a9a..46626b40 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt @@ -15,12 +15,13 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val regionRepository: OnboardingRegionRepository, - private val regionCurrentRepository: RegionCurrentRepository + private val regionCurrentRepository: RegionCurrentRepository, ) : ViewModel() { private val _state = MutableStateFlow(HomeContract.HomeState()) @@ -33,51 +34,7 @@ class HomeViewModel @Inject constructor( init { fetchRegion() -// loadSavedLocationInfo() - regionCurrent() // 현재 지역 정보 가져오기 추가 - } - - /** - * 저장된 위치 정보를 불러와서 상태에 반영 - */ - private fun loadSavedLocationInfo() { - viewModelScope.launch { - try { - // LocationInfo와 ActiveRegion을 모두 가져오기 - val locationInfo = PreferenceDataStore.getLocationInfo().first() - val activeRegion = PreferenceDataStore.getActiveRegion().first() - - Log.d("HomeViewModel", "저장된 위치 정보 불러오기:") - Log.d("HomeViewModel", " - 구: ${locationInfo.guName} (ID: ${locationInfo.guId})") - Log.d("HomeViewModel", " - 동: ${locationInfo.dongName} (ID: ${locationInfo.dongId})") - Log.d("HomeViewModel", " - 활동지역: $activeRegion") - - _state.update { currentState -> - currentState.copy( - selectedLocation = HomeContract.LocationInfo( - selectedGuId = locationInfo.guId, - selectedDongId = locationInfo.dongId, - selectedGu = locationInfo.guName, - selectedDong = locationInfo.dongName - ) - ) - } - - // 위치 정보가 있으면 표시용 로그 - val displayLocation = if (locationInfo.guName.isNotEmpty() && locationInfo.dongName.isNotEmpty()) { - "${locationInfo.guName} ${locationInfo.dongName}" - } else if (activeRegion.isNotEmpty()) { - activeRegion - } else { - "위치를 선택해주세요" - } - - Log.d("HomeViewModel", "TopBar 표시 위치: $displayLocation") - - } catch (e: Exception) { - Log.e("HomeViewModel", "저장된 위치 정보 불러오기 실패: ${e.message}") - } - } + regionCurrent() } fun toggleLocationMenu() { @@ -108,7 +65,6 @@ class HomeViewModel @Inject constructor( isLocationMenuVisible = false ) } - Log.d("HomeViewModel", "구 선택 완료: $guName (ID: $guId)") } catch (e: Exception) { Log.e("HomeViewModel", "구 선택 저장 실패: ${e.message}") @@ -137,12 +93,11 @@ class HomeViewModel @Inject constructor( ) ) } - - Log.d("HomeViewModel", "동 선택 완료: $dongName (ID: $dongId)") - Log.d("HomeViewModel", "전체 위치: ${currentLocation.selectedGu} $dongName") + Timber.d("HomeViewmodel", "동 선택 완료: $dongName (ID: $dongId)") } catch (e: Exception) { - Log.e("HomeViewModel", "동 선택 저장 실패: ${e.message}") + Timber.e("HomeViewmodel", "동 선택 저장 실패: ${e.message}") } + } } @@ -151,7 +106,7 @@ class HomeViewModel @Inject constructor( viewModelScope.launch { try { - val result = regionCurrentRepository.RegionCurrent(userId.first()) + val result = regionCurrentRepository.regionCurrent(userId.first()) result.onSuccess { response -> Log.d("HomeViewModel", "RegionCurrent 성공: ${response.fullRegionName}") PreferenceDataStore.saveActiveRegion(response.fullRegionName) @@ -256,42 +211,4 @@ class HomeViewModel @Inject constructor( } } } - - fun clearError() { - _state.update { - it.copy( - uiState = it.uiState.copy(error = null) - ) - } - } - - fun refreshPosts() { - _state.update { it.copy(uiState = it.uiState.copy(isLoading = true)) } - - viewModelScope.launch { - try { - // 여기에 실제 포스트 데이터를 가져오는 로직 추가 - // val result = postsRepository.getPosts(...) - - _state.update { - it.copy( - uiState = it.uiState.copy( - isLoading = false, - error = null - ) - ) - } - } catch (e: Exception) { - Log.e("HomeViewModel", "refreshPosts Exception: ${e.message}") - _state.update { - it.copy( - uiState = it.uiState.copy( - isLoading = false, - error = e.message - ) - ) - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/navigation/LoginNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/login/navigation/LoginNavigation.kt index 252d31df..9b7df2e8 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/navigation/LoginNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/navigation/LoginNavigation.kt @@ -23,6 +23,7 @@ fun NavGraphBuilder.loginNavGraph( paddingValues: PaddingValues, navigateUp: () -> Unit, navigateNext: () -> Unit, + navigateHome: () -> Unit, snackBarHostState: SnackbarHostState ) { composable { @@ -30,6 +31,7 @@ fun NavGraphBuilder.loginNavGraph( paddingValues = paddingValues, navigateUp = navigateUp, navigateNext = navigateNext, + navigateHome = navigateHome, snackBarHostState = snackBarHostState ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt b/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt index c5781e95..fe31c3c4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt @@ -2,23 +2,21 @@ package com.paw.key.presentation.ui.login.state import androidx.compose.runtime.Immutable -class LoginContract { - @Immutable - data class LoginState( - val email: String = "", - val password: String = "", +@Immutable +data class LoginState( + val email: String = "", + val password: String = "", - val isPasswordVisible: Boolean = false - ) { - val isLoginValid get() = email.isNotBlank() && password.isNotBlank() - } + val isPasswordVisible: Boolean = false, +) { + val isLoginValid get() = email.isNotBlank() && password.isNotBlank() +} - sealed class LoginSideEffect { - data class ShowSnackBar(val message: String) : LoginSideEffect() - data object NavigateUp: LoginSideEffect() - data object NavigateNext: LoginSideEffect() +sealed class LoginSideEffect { + data class ShowSnackBar(val message: String) : LoginSideEffect() + data object NavigateUp : LoginSideEffect() + data object NavigateNext : LoginSideEffect() - data object SignInSucceed : LoginSideEffect() - data object SignInFailed : LoginSideEffect() - } + data object SignInSucceed : LoginSideEffect() + data object SignInFailed : LoginSideEffect() } \ 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 eb82ebf6..4de58106 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,22 +1,23 @@ package com.paw.key.presentation.ui.login.viewmodel import androidx.lifecycle.ViewModel -import androidx.navigation.NavController -import com.paw.key.presentation.ui.login.state.LoginContract +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 kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject class LoginViewModel @Inject constructor( - + private val authRepository: AuthRepository, ) : ViewModel() { - private val _state = MutableStateFlow(LoginContract.LoginState()) - val state : StateFlow + private val _state = MutableStateFlow(LoginState()) + val state: StateFlow get() = _state.asStateFlow() - private val _sideEffect = MutableStateFlow(null) - val sideEffect : StateFlow + private val _sideEffect = MutableStateFlow(null) + val sideEffect: StateFlow get() = _sideEffect.asStateFlow() fun onEmailChanged(email: String) { @@ -38,10 +39,4 @@ class LoginViewModel @Inject constructor( } - fun onClickSignUp(navController: NavController, email: String, password: String) { - val encodedEmail = java.net.URLEncoder.encode(email, "UTF-8") - val encodedPassword = java.net.URLEncoder.encode(password, "UTF-8") - navController.navigate("signup/$encodedEmail/$encodedPassword") - } - } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt index cdefe00d..92a0b3a4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt @@ -57,7 +57,7 @@ import coil.compose.AsyncImage import com.paw.key.R import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.core.util.noRippleClickable +import com.paw.key.core.extension.noRippleClickable import com.paw.key.presentation.ui.signup.component.FormField import com.paw.key.presentation.ui.signup.component.SignUpTextField import com.paw.key.presentation.ui.signup.component.SignUpUserSelectButton diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 810d2f11..1aa8c176 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,11 @@ timber = "5.0.1" kakaoMaps = "2.9.5" v2All = "2.20.1" +# Google +credentials = "1.5.0" +googleid = "1.1.1" +desugar = "2.1.3" + # ServiceLocation playServicesLocation = "21.3.0" @@ -128,6 +133,12 @@ naver-map-compose = { group = "io.github.fornewid", name = "naver-map-compose", naver-map-location = { group = "io.github.fornewid", name = "naver-map-location", version.ref = "naverMapLocation" } naver-map-sdk = { group = "com.naver.maps", name = "map-sdk", version.ref = "naverMapSdk" } +# Google +androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "credentials" } +androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentials" } +googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } +desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }