From 2345816e78bfa8d8c204c7f2051bb0d3197f55cd Mon Sep 17 00:00:00 2001 From: Kez Date: Sun, 4 May 2025 14:12:17 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Server=20Login=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/hmh/hamyeonham/StartActivity.kt | 2 +- .../hamyeonham/common/context/ContextExt.kt | 2 + .../auth/authenticator/AuthenticatorUtil.kt | 4 +- .../auth/authenticator/HMHAuthenticator.kt | 4 +- ...Preference.kt => DefaultUserPreference.kt} | 10 +- ...NetworkPreference.kt => UserPreference.kt} | 4 +- .../auth/interceptor/HeaderInterceptor.kt | 4 +- .../core/network/di/DataStoreModule.kt | 6 +- .../core/network/login/model/LoginResponse.kt | 2 +- .../network/signup/model/SignUpResponse.kt | 2 +- data/login/build.gradle.kts | 4 + .../login/datasource/AuthDataSource.kt | 9 + .../login/datasource/AuthDataSourceFactory.kt | 13 ++ .../datasource/kakao/KakaoAuthCodeClient.kt | 198 ++++++++++++++++++ .../kakao/KakaoAuthDataSourceImpl.kt | 95 +++++++++ .../com/hmh/hamyeonham/login/di/AuthBinder.kt | 45 ++++ .../hamyeonham/login/di/AuthDataSourceKey.kt | 7 + .../com/hmh/hamyeonham/login/di/AuthModule.kt | 21 -- .../login/repository/AuthRepositoryFactory.kt | 27 +++ .../{ => repository}/DefaultAuthRepository.kt | 31 +-- .../DefaultAuthRepositorySelector.kt | 12 ++ .../login/repository/LocalAuthRepository.kt | 52 +++++ .../hmh/hamyeonham/login/di/AuthProvider.kt | 7 + .../com/hmh/hamyeonham/login/model/Login.kt | 2 +- .../hmh/hamyeonham/login/model/SignUpUser.kt | 2 +- .../login/repository/AuthRepository.kt | 12 +- .../repository/AuthRepositorySelector.kt | 7 + .../hamyeonham/login/usecase/AuthUseCase.kt | 32 +++ .../hamyeonham/feature/login/LoginActivity.kt | 14 +- .../feature/login/LoginViewModel.kt | 95 ++------- .../mypage/viewmodel/MyPageViewModel.kt | 12 +- .../feature/onboarding/OnBoardingActivity.kt | 41 ++-- .../viewmodel/OnBoardingViewModel.kt | 70 ++----- 33 files changed, 630 insertions(+), 218 deletions(-) rename core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/{DefaultHMHNetworkPreference.kt => DefaultUserPreference.kt} (87%) rename core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/{HMHNetworkPreference.kt => UserPreference.kt} (79%) create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthDataSourceKey.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthModule.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt rename data/login/src/main/java/com/hmh/hamyeonham/login/{ => repository}/DefaultAuthRepository.kt (75%) create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/di/AuthProvider.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt diff --git a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt index a61918f1d..de6c6e726 100644 --- a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt +++ b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt @@ -35,7 +35,7 @@ class StartActivity : AppCompatActivity() { confirmButtonText = "확인", ) .setConfirmButtonClickListener { - finish() + navigateToLogin() } .showAllowingStateLoss(supportFragmentManager) diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt index 60e17dec0..21d062647 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt @@ -1,8 +1,10 @@ package com.hmh.hamyeonham.common.context import android.Manifest +import android.app.Activity import android.app.Dialog import android.content.Context +import android.content.ContextWrapper import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Point diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/AuthenticatorUtil.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/AuthenticatorUtil.kt index abb100e56..c4501a00a 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/AuthenticatorUtil.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/AuthenticatorUtil.kt @@ -3,7 +3,7 @@ package com.hmh.hamyeonham.core.network.auth.authenticator import android.content.Context import com.hmh.hamyeonham.common.navigation.NavigationProvider import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference import com.jakewharton.processphoenix.ProcessPhoenix import com.kakao.sdk.user.UserApiClient import dagger.hilt.android.qualifiers.ApplicationContext @@ -18,7 +18,7 @@ import kotlin.coroutines.resume @Singleton class AuthenticatorUtil @Inject constructor( @ApplicationContext private val context: Context, - private val dataStore: HMHNetworkPreference, + private val dataStore: UserPreference, private val databaseManager: DatabaseManager, private val navigationProvider: NavigationProvider, ) { diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/HMHAuthenticator.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/HMHAuthenticator.kt index c7dbca56e..33dffa7f2 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/HMHAuthenticator.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/HMHAuthenticator.kt @@ -1,7 +1,7 @@ package com.hmh.hamyeonham.core.network.auth.authenticator import com.hmh.hamyeonham.core.network.auth.api.RefreshService -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -16,7 +16,7 @@ import javax.inject.Singleton @Singleton class HMHAuthenticator @Inject constructor( - private val dataStore: HMHNetworkPreference, + private val dataStore: UserPreference, private val api: RefreshService, private val authenticatorUtil: AuthenticatorUtil ) : Authenticator { diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultHMHNetworkPreference.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultUserPreference.kt similarity index 87% rename from core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultHMHNetworkPreference.kt rename to core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultUserPreference.kt index bf611d3d2..561664bf6 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultHMHNetworkPreference.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/DefaultUserPreference.kt @@ -6,9 +6,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class DefaultHMHNetworkPreference @Inject constructor( +class DefaultUserPreference @Inject constructor( private val preferences: SharedPreferences, -) : HMHNetworkPreference { +) : UserPreference { override var accessToken: String get() = preferences.getString("access_token", "").orEmpty() set(value) { @@ -30,11 +30,11 @@ class DefaultHMHNetworkPreference @Inject constructor( putString("user_name", value) } } - override var userId: Int - get() = preferences.getInt("user_id", -1) + override var userId: Long + get() = preferences.getLong("user_id", -1) set(value) { preferences.edit(commit = true) { - putInt("user_id", value) + putLong("user_id", value) } } override var autoLoginConfigured: Boolean diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/HMHNetworkPreference.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/UserPreference.kt similarity index 79% rename from core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/HMHNetworkPreference.kt rename to core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/UserPreference.kt index 538fedd5a..6954fa3be 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/HMHNetworkPreference.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/datastore/network/UserPreference.kt @@ -1,10 +1,10 @@ package com.hmh.hamyeonham.core.network.auth.datastore.network -interface HMHNetworkPreference { +interface UserPreference { var accessToken: String var refreshToken: String var userName: String - var userId: Int + var userId: Long var autoLoginConfigured: Boolean fun clear() } diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/interceptor/HeaderInterceptor.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/interceptor/HeaderInterceptor.kt index 17c84e44d..2892ef923 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/interceptor/HeaderInterceptor.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/interceptor/HeaderInterceptor.kt @@ -1,13 +1,13 @@ package com.hmh.hamyeonham.core.network.auth.interceptor -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference import kotlinx.datetime.TimeZone import okhttp3.Interceptor import okhttp3.Response import javax.inject.Inject class HeaderInterceptor @Inject constructor( - private val dataStore: HMHNetworkPreference + private val dataStore: UserPreference ) : Interceptor { private val encodedToken: String get() = "Bearer ${dataStore.accessToken}" diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/DataStoreModule.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/DataStoreModule.kt index a5607c37d..ad91f70fe 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/DataStoreModule.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/DataStoreModule.kt @@ -5,8 +5,8 @@ import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.hmh.hamyeonham.core.network.BuildConfig -import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultHMHNetworkPreference -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultUserPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference import dagger.Binds import dagger.Module import dagger.Provides @@ -71,6 +71,6 @@ object DataStoreModule { interface Binder { @Singleton @Binds - fun bindAppPreferences(dataStore: DefaultHMHNetworkPreference): HMHNetworkPreference + fun bindAppPreferences(dataStore: DefaultUserPreference): UserPreference } } diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt index 904b8386b..f6d65914e 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt @@ -9,7 +9,7 @@ data class LoginResponse( @SerialName("token") val token: Token? = null, @SerialName("userId") - val userId: Int? = null + val userId: Long? = null ) { @Serializable data class Token( diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt index 857504d2e..55c649372 100644 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt +++ b/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt @@ -10,7 +10,7 @@ data class SignUpResponse( @SerialName("token") val token: LoginResponse.Token? = null, @SerialName("userId") - val userId: Int, + val userId: Long, ) { @Serializable data class Token( diff --git a/data/login/build.gradle.kts b/data/login/build.gradle.kts index 919f28065..18e596a74 100644 --- a/data/login/build.gradle.kts +++ b/data/login/build.gradle.kts @@ -11,4 +11,8 @@ dependencies { implementation(projects.core.common) implementation(projects.domain.login) implementation(projects.core.network) + implementation(projects.core.database) + + // kakao + implementation(libs.kakao.login) } diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt new file mode 100644 index 000000000..041b6a0b4 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt @@ -0,0 +1,9 @@ +package com.hmh.hamyeonham.login.datasource + +import com.kakao.sdk.user.model.User + +interface AuthDataSource { + suspend fun login(): Result + suspend fun fetchUserProfile(): Result + suspend fun logout(): Result +} diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt new file mode 100644 index 000000000..6f7fd9501 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt @@ -0,0 +1,13 @@ +package com.hmh.hamyeonham.login.datasource + +import com.hmh.hamyeonham.login.di.AuthProvider +import jakarta.inject.Inject +import jakarta.inject.Singleton + +@Singleton +class AuthDataSourceFactory @Inject constructor( + private val sources: Map +) { + fun get(provider: AuthProvider): AuthDataSource = + sources[provider] ?: error("No AuthDataSource bound for $provider") +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt new file mode 100644 index 000000000..3e162b804 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt @@ -0,0 +1,198 @@ +package com.hmh.hamyeonham.login.datasource.kakao + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Base64 +import com.kakao.sdk.auth.Constants +import com.kakao.sdk.auth.IntentFactory +import com.kakao.sdk.auth.SingleResultReceiver +import com.kakao.sdk.auth.UriUtility +import com.kakao.sdk.auth.model.Prompt +import com.kakao.sdk.common.KakaoSdk +import com.kakao.sdk.common.model.ApplicationInfo +import com.kakao.sdk.common.model.ApprovalType +import com.kakao.sdk.common.model.AuthError +import com.kakao.sdk.common.model.AuthErrorCause +import com.kakao.sdk.common.model.AuthErrorResponse +import com.kakao.sdk.common.model.ClientError +import com.kakao.sdk.common.model.ClientErrorCause +import com.kakao.sdk.common.model.ContextInfo +import com.kakao.sdk.common.util.IntentResolveClient +import com.kakao.sdk.common.util.KakaoJson +import com.kakao.sdk.common.util.SdkLog +import java.net.HttpURLConnection +import java.security.MessageDigest +import java.util.UUID + +class KakaoAuthCodeClient( + private val intentResolveClient: IntentResolveClient = IntentResolveClient.instance, + private val applicationInfo: ApplicationInfo = KakaoSdk.applicationContextInfo, + private val contextInfo: ContextInfo = KakaoSdk.applicationContextInfo, + private val approvalType: ApprovalType = KakaoSdk.approvalType, +) { + fun isKakaoTalkLoginAvailable(context: Context): Boolean = + intentResolveClient.resolveTalkIntent(context, IntentFactory.talkBase()) != null + + @JvmOverloads + fun authorizeWithKakaoTalk( + context: Context, + prompts: List? = null, + requestCode: Int = DEFAULT_REQUEST_CODE, + nonce: String? = null, + channelPublicIds: List? = null, + serviceTerms: List? = null, + codeVerifier: String? = null, + kauthTxId: String? = null, + callback: (code: String?, error: Throwable?) -> Unit, + ) { + if (!isKakaoTalkLoginAvailable(context)) { + callback(null, ClientError(ClientErrorCause.NotSupported, "KakaoTalk not installed")) + } else { + try { + context.startActivity( + IntentFactory.talk( + context, + requestCode, + clientId = applicationInfo.appKey, + redirectUri = applicationInfo.redirectUri, + kaHeader = contextInfo.kaHeader, + extras = Bundle().apply { + channelPublicIds?.let { + putString( + Constants.CHANNEL_PUBLIC_ID, + channelPublicIds.joinToString(",") + ) + } + serviceTerms?.let { + putString( + Constants.SERVICE_TERMS, + serviceTerms.joinToString(",") + ) + } + approvalType.value?.let { putString(Constants.APPROVAL_TYPE, it) } + codeVerifier?.let { + putString(Constants.CODE_CHALLENGE, codeChallenge(it.toByteArray())) + putString( + Constants.CODE_CHALLENGE_METHOD, + Constants.CODE_CHALLENGE_METHOD_VALUE + ) + } + prompts?.let { prompts -> + putString( + Constants.PROMPT, + prompts.joinToString(",") { prompt -> prompt.value } + ) + } + nonce?.let { putString(Constants.NONCE, nonce) } + kauthTxId?.let { putString(Constants.KAUTH_TX_ID, kauthTxId) } + }, + resultReceiver = resultReceiver(callback) + ).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } catch (startActivityError: Throwable) { + SdkLog.e(startActivityError) + callback(null, startActivityError) + } + } + } + + @JvmOverloads + fun authorizeWithKakaoAccount( + context: Context, + prompts: List? = null, + scopes: List? = null, + nonce: String? = null, + agt: String? = null, + channelPublicIds: List? = null, + serviceTerms: List? = null, + loginHint: String? = null, + codeVerifier: String? = null, + accountsSkipIntro: Boolean? = null, + accountsTalkLoginVisible: Boolean? = null, + kauthTxId: String? = null, + callback: (code: String?, error: Throwable?) -> Unit, + ) { + val uriUtility = UriUtility() + val uri = + uriUtility.authorize( + clientId = applicationInfo.appKey, + agt = agt, + redirectUri = applicationInfo.redirectUri, + scopes = scopes, + kaHeader = contextInfo.kaHeader, + channelPublicIds = channelPublicIds, + serviceTerms = serviceTerms, + prompts = prompts, + nonce = nonce, + loginHint = loginHint, + approvalType = approvalType.value, + codeChallenge = codeVerifier?.let { codeChallenge(it.toByteArray()) }, + codeChallengeMethod = codeVerifier?.let { Constants.CODE_CHALLENGE_METHOD_VALUE }, + accountsSkipIntro = accountsSkipIntro, + accountsTalkLoginVisible = accountsTalkLoginVisible, + kauthTxId = kauthTxId, + ) + SdkLog.i(uri) + try { + context.startActivity( + IntentFactory.account( + context, + uri, + applicationInfo.redirectUri, + resultReceiver(callback) + ) + ) + } catch (startActivityError: Throwable) { + SdkLog.e(startActivityError) + callback(null, startActivityError) + } + } + + @JvmSynthetic + internal fun resultReceiver(callback: (String?, Throwable?) -> Unit) = + SingleResultReceiver.create( + emitter = callback, + identifier = "Auth Code", + parseResponse = { uri -> uri.getQueryParameter(Constants.CODE) }, + parseError = { uri -> + // oauth spec + // error is nonNull, errorDescription is nullable + val error = uri.getQueryParameter(Constants.ERROR) ?: Constants.UNKNOWN_ERROR + val errorDescription = uri.getQueryParameter(Constants.ERROR_DESCRIPTION) + val errorCause = runCatching { + KakaoJson.fromJson(error, AuthErrorCause::class.java) + }.getOrDefault(AuthErrorCause.Unknown) + + AuthError( + HttpURLConnection.HTTP_MOVED_TEMP, + errorCause, + AuthErrorResponse(error, errorDescription) + ) + }, + isError = { uri -> uri.getQueryParameter(Constants.CODE).isNullOrEmpty() } + ) + + companion object { + @JvmStatic + val instance by lazy { KakaoAuthCodeClient() } + + const val DEFAULT_REQUEST_CODE: Int = 10012 + + fun codeVerifier(): String = + Base64.encodeToString( + MessageDigest.getInstance(Constants.CODE_VERIFIER_ALGORITHM).digest( + UUID.randomUUID().toString().toByteArray() + ), + Base64.NO_WRAP or Base64.NO_PADDING + ) + + fun codeChallenge(codeVerifier: ByteArray): String = + Base64.encodeToString( + MessageDigest.getInstance(Constants.CODE_CHALLENGE_ALGORITHM).digest(codeVerifier), + Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE + ) + } +} diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt new file mode 100644 index 000000000..22a9b7db7 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt @@ -0,0 +1,95 @@ +package com.hmh.hamyeonham.login.datasource.kakao + +import android.content.Context +import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.kakao.sdk.auth.AuthApiClient +import com.kakao.sdk.auth.AuthCodeClient.Companion.DEFAULT_REQUEST_CODE +import com.kakao.sdk.auth.model.OAuthToken +import com.kakao.sdk.user.UserApiClient +import com.kakao.sdk.user.model.User +import dagger.hilt.android.qualifiers.ApplicationContext +import jakarta.inject.Inject +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +class KakaoAuthDataSourceImpl @Inject constructor( + @ApplicationContext private val context: Context +) : AuthDataSource { + + override suspend fun login(): Result = + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + suspendCancellableCoroutine { cont -> + loginWithKakaoTalk(context) { token, error -> + when { + error != null -> cont.resume(Result.failure(error)) + token != null -> cont.resume(Result.success(token.accessToken)) + else -> cont.resume( + Result.failure( + IllegalStateException("Empty KakaoTalk token") + ) + ) + } + } + } + } else { + loginWithAccount() + } + + fun loginWithKakaoTalk( + context: Context, + requestCode: Int = DEFAULT_REQUEST_CODE, + nonce: String? = null, + channelPublicIds: List? = null, + serviceTerms: List? = null, + callback: (token: OAuthToken?, error: Throwable?) -> Unit, + ) { + val codeVerifier = KakaoAuthCodeClient.codeVerifier() + KakaoAuthCodeClient.instance.authorizeWithKakaoTalk( + context, + prompts = null, + requestCode, + nonce = nonce, + channelPublicIds = channelPublicIds, + serviceTerms = serviceTerms, + codeVerifier = codeVerifier + ) { code, codeError -> + if (codeError != null) { + callback(null, codeError) + } else { + AuthApiClient.instance.issueAccessToken(code!!, codeVerifier) { token, tokenError -> + callback(token, tokenError) + } + } + } + } + + + private suspend fun loginWithAccount(): Result = suspendCancellableCoroutine { cont -> + UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> + when { + error != null -> cont.resume(Result.failure(error)) + token != null -> cont.resume(Result.success(token.accessToken)) + else -> cont.resume(Result.failure(IllegalStateException("Empty KakaoAccount token"))) + } + } + } + + override suspend fun fetchUserProfile(): Result = + suspendCancellableCoroutine { cont -> + UserApiClient.instance.me { user, error -> + when { + error != null -> cont.resume(Result.failure(error)) + user != null -> cont.resume(Result.success(user)) + else -> cont.resume(Result.failure(IllegalStateException("Empty Kakao user"))) + } + } + } + + override suspend fun logout(): Result = + suspendCancellableCoroutine { cont -> + UserApiClient.instance.logout { error -> + if (error != null) cont.resume(Result.failure(error)) + else cont.resume(Result.success(Unit)) + } + } +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt new file mode 100644 index 000000000..64ac3be25 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt @@ -0,0 +1,45 @@ +package com.hmh.hamyeonham.login.di + +import com.hmh.hamyeonham.login.repository.DefaultAuthRepository +import com.hmh.hamyeonham.login.repository.LocalAuthRepository +import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.hmh.hamyeonham.login.datasource.kakao.KakaoAuthDataSourceImpl +import com.hmh.hamyeonham.login.repository.AuthRepository +import com.hmh.hamyeonham.login.repository.AuthRepositorySelector +import com.hmh.hamyeonham.login.repository.DefaultAuthRepositorySelector +import com.hmh.hamyeonham.login.repository.LocalAuth +import com.hmh.hamyeonham.login.repository.RemoteAuth +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.IntoMap +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface AuthBinder { + @Binds + @Singleton + @RemoteAuth + fun bindDefaultAuthRepository(loginRepository: DefaultAuthRepository): AuthRepository + + @Binds + @Singleton + @LocalAuth + fun bindLocalAuthRepository( + localAuthRepository: LocalAuthRepository + ): AuthRepository + + @Binds + @Singleton + @IntoMap + @AuthDataSourceKey(AuthProvider.KAKAO) + fun bindKakaoAuthDataSource(kakaoAuthDataSourceImpl: KakaoAuthDataSourceImpl): AuthDataSource + + @Binds + @Singleton + fun bindAuthRepositorySelector( + defaultAuthRepositorySelector: DefaultAuthRepositorySelector + ): AuthRepositorySelector +} diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthDataSourceKey.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthDataSourceKey.kt new file mode 100644 index 000000000..5db7fa34c --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthDataSourceKey.kt @@ -0,0 +1,7 @@ +package com.hmh.hamyeonham.login.di + +import dagger.MapKey + +@MapKey +@Retention(AnnotationRetention.BINARY) +annotation class AuthDataSourceKey(val value: AuthProvider) \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthModule.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthModule.kt deleted file mode 100644 index fdd18d7ef..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.hmh.hamyeonham.login.di - -import com.hmh.hamyeonham.login.DefaultAuthRepository -import com.hmh.hamyeonham.login.repository.AuthRepository -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object AuthModule { - @Module - @InstallIn(SingletonComponent::class) - interface Binder { - @Binds - @Singleton - fun provideUsageGoalsRepository(loginRepository: DefaultAuthRepository): AuthRepository - } -} diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt new file mode 100644 index 000000000..418f73688 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt @@ -0,0 +1,27 @@ +package com.hmh.hamyeonham.login.repository + +import com.hmh.hamyeonham.core.database.manger.DatabaseManager +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference +import com.hmh.hamyeonham.core.network.login.AuthService +import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.hmh.hamyeonham.login.di.AuthProvider +import jakarta.inject.Inject + +class AuthRepositoryFactory @Inject constructor( + private val authService: AuthService, + private val dataSourceMap: Map, + private val preference: UserPreference, + private val db: DatabaseManager +) { + fun createRemoteRepository(provider: AuthProvider): AuthRepository { + val dataSource = dataSourceMap[provider] + ?: throw IllegalArgumentException("Invalid provider") + return DefaultAuthRepository(authService, dataSource) + } + + fun createLocalRepository(provider: AuthProvider): AuthRepository { + val dataSource = dataSourceMap[provider] + ?: throw IllegalArgumentException("Invalid provider") + return LocalAuthRepository(dataSource, preference, db) + } +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/DefaultAuthRepository.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt similarity index 75% rename from data/login/src/main/java/com/hmh/hamyeonham/login/DefaultAuthRepository.kt rename to data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt index ca5af6537..95a7b7144 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/DefaultAuthRepository.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt @@ -1,26 +1,35 @@ -package com.hmh.hamyeonham.login +package com.hmh.hamyeonham.login.repository import com.hmh.hamyeonham.core.network.login.AuthService import com.hmh.hamyeonham.core.network.login.model.LoginRequest import com.hmh.hamyeonham.core.network.signup.model.toSignUpRequest +import com.hmh.hamyeonham.login.datasource.AuthDataSource import com.hmh.hamyeonham.login.mapper.toLogin import com.hmh.hamyeonham.login.model.Login import com.hmh.hamyeonham.login.model.SignRequestDomain import com.hmh.hamyeonham.login.model.SignUpUser -import com.hmh.hamyeonham.login.repository.AuthRepository import kotlinx.datetime.TimeZone -import javax.inject.Inject -class DefaultAuthRepository @Inject constructor( +class DefaultAuthRepository( private val authService: AuthService, + private val authDataSource: AuthDataSource ) : AuthRepository { + override suspend fun login(): Result { + val accessToken = authDataSource.login().getOrThrow() + val request = LoginRequest("KAKAO") // 실제 provider 이름 필요 + val bearerToken = "Bearer $accessToken" + return runCatching { + authService.login(bearerToken, request).data.toLogin() + } + } + override suspend fun signUp( accessToken: String, - signUpRequest: SignRequestDomain, + signUpRequest: SignRequestDomain ): Result { + val bearerToken = "Bearer $accessToken" return runCatching { - val bearerToken = "Bearer $accessToken" authService.signUp( bearerToken, "Android", @@ -30,14 +39,6 @@ class DefaultAuthRepository @Inject constructor( } } - override suspend fun login(accessToken: String): Result { - val request = LoginRequest("KAKAO") - val bearerToken = "Bearer $accessToken" - return runCatching { - authService.login(bearerToken, request).data.toLogin() - } - } - override suspend fun logout(accessToken: String): Result { val bearerToken = "Bearer $accessToken" return runCatching { @@ -51,4 +52,4 @@ class DefaultAuthRepository @Inject constructor( authService.withdrawal(bearerToken) } } -} +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt new file mode 100644 index 000000000..6d643bb22 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt @@ -0,0 +1,12 @@ +package com.hmh.hamyeonham.login.repository + +import com.hmh.hamyeonham.login.di.AuthProvider +import javax.inject.Inject + +class DefaultAuthRepositorySelector @Inject constructor( + private val factory: AuthRepositoryFactory +) : AuthRepositorySelector { + override fun getRepository(provider: AuthProvider): AuthRepository { + return factory.createLocalRepository(provider) + } +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt new file mode 100644 index 000000000..28c30c240 --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt @@ -0,0 +1,52 @@ +package com.hmh.hamyeonham.login.repository + +import com.hmh.hamyeonham.core.database.manger.DatabaseManager +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference +import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.hmh.hamyeonham.login.model.Login +import com.hmh.hamyeonham.login.model.SignRequestDomain +import com.hmh.hamyeonham.login.model.SignUpUser + +class LocalAuthRepository( + private val authDataSource: AuthDataSource, + private val preference: UserPreference, + private val db: DatabaseManager, +) : AuthRepository { + + override suspend fun login(): Result = runCatching { + val accessToken = authDataSource.login().getOrThrow() + val kakaoUser = authDataSource.fetchUserProfile().getOrThrow() + + preference.apply { + this.accessToken = accessToken + this.userId = kakaoUser.id ?: -1 + this.autoLoginConfigured = true + } + + Login( + accessToken = accessToken, + refreshToken = "", + userId = kakaoUser.id ?: -1 + ) + } + + override suspend fun signUp( + accessToken: String, + signUpRequest: SignRequestDomain + ): Result = + Result.success( + SignUpUser( + userId = preference.userId, + accessToken = accessToken, + refreshToken = "" + ) + ) + + override suspend fun logout(accessToken: String): Result = runCatching { + authDataSource.logout().getOrThrow() + preference.clear() + db.deleteAll() + } + + override suspend fun withdrawal(accessToken: String): Result = logout(accessToken) +} \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/di/AuthProvider.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/di/AuthProvider.kt new file mode 100644 index 000000000..95fa96913 --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/di/AuthProvider.kt @@ -0,0 +1,7 @@ +package com.hmh.hamyeonham.login.di + +enum class AuthProvider { + KAKAO, + GOOGLE, + EMAIL, +} \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/Login.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/Login.kt index dfbc61c9f..1763b08c3 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/Login.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/Login.kt @@ -1,7 +1,7 @@ package com.hmh.hamyeonham.login.model data class Login( - val userId: Int, + val userId: Long, val accessToken: String, val refreshToken: String, ) diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignUpUser.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignUpUser.kt index 2370b38ed..93d29da26 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignUpUser.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignUpUser.kt @@ -1,7 +1,7 @@ package com.hmh.hamyeonham.login.model data class SignUpUser( - val userId: Int, + val userId: Long, val accessToken: String, val refreshToken: String, ) diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt index 4536369e2..f75d8a6d3 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt @@ -1,11 +1,21 @@ package com.hmh.hamyeonham.login.repository +import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.model.Login import com.hmh.hamyeonham.login.model.SignRequestDomain import com.hmh.hamyeonham.login.model.SignUpUser +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class RemoteAuth + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class LocalAuth interface AuthRepository { - suspend fun login(accessToken: String): Result + suspend fun login(): Result suspend fun logout(accessToken: String): Result suspend fun signUp(accessToken: String, signUpRequest: SignRequestDomain): Result suspend fun withdrawal(accessToken: String): Result diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt new file mode 100644 index 000000000..436534a4d --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt @@ -0,0 +1,7 @@ +package com.hmh.hamyeonham.login.repository + +import com.hmh.hamyeonham.login.di.AuthProvider + +interface AuthRepositorySelector { + fun getRepository(provider: AuthProvider): AuthRepository +} \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt new file mode 100644 index 000000000..572c333b9 --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt @@ -0,0 +1,32 @@ +package com.hmh.hamyeonham.login.usecase + +import com.hmh.hamyeonham.login.di.AuthProvider +import com.hmh.hamyeonham.login.model.Login +import com.hmh.hamyeonham.login.model.SignRequestDomain +import com.hmh.hamyeonham.login.model.SignUpUser +import com.hmh.hamyeonham.login.repository.AuthRepositorySelector +import javax.inject.Inject + +class AuthUseCase @Inject constructor( + private val selector: AuthRepositorySelector +) { + suspend fun login(provider: AuthProvider): Result { + return selector.getRepository(provider).login() + } + + suspend fun logout(accessToken: String, provider: AuthProvider): Result { + return selector.getRepository(provider).logout(accessToken) + } + + suspend fun signUp( + accessToken: String, + provider: AuthProvider, + request: SignRequestDomain + ): Result { + return selector.getRepository(provider).signUp(accessToken, request) + } + + suspend fun withdrawal(accessToken: String, provider: AuthProvider): Result { + return selector.getRepository(provider).withdrawal(accessToken) + } +} diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt index 506ad101b..860c14201 100644 --- a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt +++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt @@ -9,7 +9,6 @@ import com.hmh.hamyeonham.common.context.toast import com.hmh.hamyeonham.common.navigation.NavigationProvider import com.hmh.hamyeonham.common.view.viewBinding import com.hmh.hamyeonham.feature.login.databinding.ActivityLoginBinding -import com.hmh.hamyeonham.feature.onboarding.OnBoardingActivity import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -35,7 +34,7 @@ class LoginActivity : AppCompatActivity() { setContentView(binding.root) binding.ivKakaoLogin.setOnClickListener { - viewModel.loginWithKakaoApp(this) + viewModel.loginWithKakaoApp() } setLoginViewPager() handleKakaoLoginSuccess() @@ -55,7 +54,7 @@ class LoginActivity : AppCompatActivity() { when (state) { is LoginEffect.LoginSuccess -> navigateToMainActivity() is LoginEffect.LoginFail -> toast(getString(R.string.fail_kakao_login)) - is LoginEffect.RequireSignUp -> navigateToOnBoardingActivity(state.token) + is LoginEffect.RequireSignUp -> navigateToOnBoardingActivity() } }.launchIn(lifecycleScope) } @@ -89,15 +88,8 @@ class LoginActivity : AppCompatActivity() { autoScrollJob.cancel() } - private fun navigateToOnBoardingActivity(accessToken: String? = null) { - if (accessToken == null) { - toast(getString(R.string.empty_token_retry_login)) - } - + private fun navigateToOnBoardingActivity() { val intent = navigationProvider.toOnBoarding() - accessToken?.let { - intent.putExtra(OnBoardingActivity.EXTRA_ACCESS_TOKEN, it) - } startActivity(intent) finish() } diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt index 16ba0a185..1c88c4375 100644 --- a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt +++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt @@ -1,22 +1,17 @@ package com.hmh.hamyeonham.feature.login -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils -import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference -import com.hmh.hamyeonham.login.repository.AuthRepository -import com.kakao.sdk.common.model.ClientError -import com.kakao.sdk.common.model.ClientErrorCause -import com.kakao.sdk.user.UserApiClient +import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference +import com.hmh.hamyeonham.login.di.AuthProvider +import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import retrofit2.HttpException import javax.inject.Inject sealed interface LoginEffect { @@ -24,7 +19,7 @@ sealed interface LoginEffect { data object LoginFail : LoginEffect - data class RequireSignUp(val token: String) : LoginEffect + data object RequireSignUp : LoginEffect } data class LoginState( @@ -33,9 +28,8 @@ data class LoginState( @HiltViewModel class LoginViewModel @Inject constructor( - private val authRepository: AuthRepository, - private val hmhNetworkPreference: HMHNetworkPreference, - private val databaseManager: DatabaseManager, + private val authUseCase: AuthUseCase, + private val userPreference: UserPreference, ) : ViewModel() { private val _kakaoLoginEvent = MutableSharedFlow() val kakaoLoginEvent = _kakaoLoginEvent.asSharedFlow() @@ -50,76 +44,21 @@ class LoginViewModel @Inject constructor( private fun updateLoginState() { val currentState = loginState.value _loginState.value = currentState.copy( - autoLogin = hmhNetworkPreference.autoLoginConfigured, + autoLogin = userPreference.autoLoginConfigured, ) } - fun loginWithKakaoApp(context: Context) { - if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { - UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> - if (error != null) { - if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { - return@loginWithKakaoTalk - } - loginWithKakaoAccount(context) - } else if (token != null) { - viewModelScope.launch { - authRepository - .login(token.accessToken) - .onSuccess { - hmhNetworkPreference.run { - accessToken = it.accessToken - refreshToken = it.refreshToken - userId = it.userId - autoLoginConfigured = true - } - _kakaoLoginEvent.emit(LoginEffect.LoginSuccess) - AmplitudeUtils.trackEventWithProperties("click_onboarding_kakao") - }.onFailure { - if (it is HttpException && it.code() == 403) { - hmhNetworkPreference.clear() - databaseManager.deleteAll() - _kakaoLoginEvent.emit(LoginEffect.RequireSignUp(token.accessToken)) - } else { - _kakaoLoginEvent.emit(LoginEffect.LoginFail) - } - } - } + fun loginWithKakaoApp() { + viewModelScope.launch { + authUseCase.login(AuthProvider.KAKAO) + .onSuccess { + // TODO if (온보딩을 타야하는 경우) _kakaoLoginEvent.emit(LoginEffect.RequireSignUp) + _kakaoLoginEvent.emit(LoginEffect.LoginSuccess) + AmplitudeUtils.trackEventWithProperties("click_onboarding_kakao") + }.onFailure { + android.util.Log.e("LoginViewModel","loginWithKakaoApp failed", it) + _kakaoLoginEvent.emit(LoginEffect.LoginFail) } - } - } else { - loginWithKakaoAccount(context) - } - } - - private fun loginWithKakaoAccount(context: Context) { - UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> - if (error != null) { - // 닉네임 정보 얻기 실패 시 - } else if (token != null) { - viewModelScope.launch { - authRepository - .login(token.accessToken) - .onSuccess { - hmhNetworkPreference.run { - accessToken = it.accessToken - refreshToken = it.refreshToken - userId = it.userId - autoLoginConfigured = true - } - _kakaoLoginEvent.emit(LoginEffect.LoginSuccess) - AmplitudeUtils.trackEventWithProperties("click_onboarding_kakao") - }.onFailure { - if (it is HttpException && it.code() == 403) { - hmhNetworkPreference.clear() - databaseManager.deleteAll() - _kakaoLoginEvent.emit(LoginEffect.RequireSignUp(token.accessToken)) - } else { - _kakaoLoginEvent.emit(LoginEffect.LoginFail) - } - } - } - } } } } diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt index 9566409b7..558c80e96 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt @@ -4,8 +4,10 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultHMHNetworkPreference +import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultUserPreference +import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.repository.AuthRepository +import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -23,8 +25,8 @@ sealed interface UserEffect { @HiltViewModel class MyPageViewModel @Inject constructor( - private val authRepository: AuthRepository, - private val hmhPreference: DefaultHMHNetworkPreference, + private val authUseCase: AuthUseCase, + private val hmhPreference: DefaultUserPreference, private val databaseManager: DatabaseManager ) : ViewModel() { @@ -33,7 +35,7 @@ class MyPageViewModel @Inject constructor( fun handleLogout() { viewModelScope.launch { - authRepository.logout(hmhPreference.accessToken).onSuccess { + authUseCase.logout(hmhPreference.accessToken, AuthProvider.KAKAO).onSuccess { deleteAllDatabase() clearPreference() _userEffect.emit(UserEffect.LogoutSuccess) @@ -45,7 +47,7 @@ class MyPageViewModel @Inject constructor( fun handleWithdrawal() { viewModelScope.launch { - authRepository.withdrawal(hmhPreference.accessToken).onSuccess { + authUseCase.withdrawal(hmhPreference.accessToken, AuthProvider.KAKAO).onSuccess { deleteAllDatabase() clearPreference() _userEffect.emit(UserEffect.WithdrawalSuccess) diff --git a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt index 8086f5851..8482875ae 100644 --- a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt +++ b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt @@ -27,9 +27,6 @@ import org.json.JSONObject @AndroidEntryPoint class OnBoardingActivity : AppCompatActivity() { - companion object { - const val EXTRA_ACCESS_TOKEN = "extra_access_token" - } private val binding by viewBinding(ActivityOnBoardingBinding::inflate) private val viewModel by viewModels() @@ -44,7 +41,6 @@ class OnBoardingActivity : AppCompatActivity() { collectOnboardingState() collectSignUpEffect() changeOnBoardingButtonTextState() - updateAccessToken() changeProgressbarVisibleState() updateBackButtonVisibility() } @@ -57,11 +53,6 @@ class OnBoardingActivity : AppCompatActivity() { }.launchIn(lifecycleScope) } - private fun updateAccessToken() { - val accessToken = intent.getStringExtra(EXTRA_ACCESS_TOKEN) - viewModel.sendEvent(OnboardEvent.UpdateAccessToken(accessToken.orEmpty())) - } - private fun collectSignUpEffect() { viewModel.onboardEffect .flowWithLifecycle(lifecycle) @@ -121,18 +112,38 @@ class OnBoardingActivity : AppCompatActivity() { OnBoardingFragmentType.SELECT_SCREEN_TIME_GOAL -> { AmplitudeUtils.trackEventWithProperties("click_challenge_totaltime") } + OnBoardingFragmentType.SELECT_DATA_TIME -> { - val property = JSONObject().put("answer_value", viewModel.onBoardingState.value.usuallyUseTimeButtonIndex) // Int - AmplitudeUtils.trackEventWithProperties("click_survey1_answer", property) + val property = JSONObject().put( + "answer_value", + viewModel.onBoardingState.value.usuallyUseTimeButtonIndex + ) // Int + AmplitudeUtils.trackEventWithProperties( + "click_survey1_answer", + property + ) } + OnBoardingFragmentType.SELECT_DATA_PROBLEM -> { - val property = JSONObject().put("answer_value", viewModel.onBoardingState.value.problemsButtonIndex) // List - AmplitudeUtils.trackEventWithProperties("click_survey2_answer", property) + val property = JSONObject().put( + "answer_value", + viewModel.onBoardingState.value.problemsButtonIndex + ) // List + AmplitudeUtils.trackEventWithProperties( + "click_survey2_answer", + property + ) } + OnBoardingFragmentType.SELECT_DATA_PERIOD -> { - val property = JSONObject().put("period", viewModel.onBoardingState.value.period) - AmplitudeUtils.trackEventWithProperties("click_challenge_period_answer", property) + val property = + JSONObject().put("period", viewModel.onBoardingState.value.period) + AmplitudeUtils.trackEventWithProperties( + "click_challenge_period_answer", + property + ) } + else -> Unit } navigateToNextViewPager(viewPager, currentItem) diff --git a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/OnBoardingViewModel.kt b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/OnBoardingViewModel.kt index 3e7900479..fec290b56 100644 --- a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/OnBoardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/viewmodel/OnBoardingViewModel.kt @@ -4,9 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils import com.hmh.hamyeonham.common.time.timeToMs -import com.hmh.hamyeonham.core.network.auth.datastore.network.HMHNetworkPreference import com.hmh.hamyeonham.login.model.SignRequestDomain -import com.hmh.hamyeonham.login.repository.AuthRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -33,7 +31,6 @@ sealed interface OnboardEvent { data class UpdateAppGoalTimeMinute(val goalTimeMinute: Int) : OnboardEvent data class UpdateAppGoalTimeHour(val goalTimeHour: Int) : OnboardEvent data class UpdateNextButtonActive(val isNextButtonActive: Boolean) : OnboardEvent - data class UpdateAccessToken(val accessToken: String) : OnboardEvent data class ChangeActivityButtonText(val buttonText: String) : OnboardEvent data class VisibleProgressbar(val progressbarVisible: Boolean) : OnboardEvent data class UpdateBackButtonActive(val isBackButtonActive: Boolean) : OnboardEvent @@ -57,7 +54,6 @@ data class OnBoardingState( val appGoalTimeHour: Int = 0, val isNextButtonActive: Boolean = false, val isBackButtonActive: Boolean = true, - val accessToken: String = "", val buttonText: String = "다음", val progressbarVisible: Boolean = true, ) { @@ -72,12 +68,7 @@ data class OnBoardingState( } @HiltViewModel -class OnBoardingViewModel -@Inject -constructor( - private val authRepository: AuthRepository, - private val hmhNetworkPreference: HMHNetworkPreference, -) : ViewModel() { +class OnBoardingViewModel @Inject constructor() : ViewModel() { private val _onBoardingState = MutableStateFlow(OnBoardingState()) val onBoardingState = _onBoardingState.asStateFlow() @@ -161,12 +152,6 @@ constructor( } } - is OnboardEvent.UpdateAccessToken -> { - updateState { - copy(accessToken = event.accessToken) - } - } - is OnboardEvent.ChangeActivityButtonText -> { updateState { copy(buttonText = event.buttonText) @@ -190,47 +175,30 @@ constructor( fun signUp() { viewModelScope.launch { val state = onBoardingState.value - val token = state.accessToken - val request = getRequestDomain(state) - authRepository - .signUp(token, request) - .onSuccess { signUpUser -> - signUpUser.let { - hmhNetworkPreference.accessToken = it.accessToken - hmhNetworkPreference.refreshToken = it.refreshToken - hmhNetworkPreference.userId = it.userId - hmhNetworkPreference.autoLoginConfigured = true - } - viewModelScope.launch { - _onboardEffect.emit(OnboardEffect.OnboardSuccess) - AmplitudeUtils.trackEventWithProperties("complete_onboarding_finish") - } - }.onFailure { - viewModelScope.launch { - _onboardEffect.emit(OnboardEffect.OnboardFail) - } - } + val request = getRequestDomain(state) // TODO 나중에 온보딩 데이터를 쓰고 싶은 경우 + _onboardEffect.emit(OnboardEffect.OnboardSuccess) + AmplitudeUtils.trackEventWithProperties("complete_onboarding_finish") } } private fun getRequestDomain(state: OnBoardingState) = SignRequestDomain( challenge = - SignRequestDomain.Challenge( - period = state.period, - app = - state.appCodeList.map { appCode -> - SignRequestDomain.Challenge.App( - appCode = appCode, - goalTime = state.appGoalTime, - ) - }, - goalTime = state.goalTime, - ), + SignRequestDomain.Challenge( + period = state.period, + app = + state.appCodeList.map { appCode -> + SignRequestDomain.Challenge.App( + appCode = appCode, + goalTime = state.appGoalTime, + ) + }, + goalTime = state.goalTime, + ), onboarding = - SignRequestDomain.Onboarding( - averageUseTime = state.usuallyUseTime, - problem = state.problems, - ), + SignRequestDomain.Onboarding( + averageUseTime = state.usuallyUseTime, + problem = state.problems, + ), ) } From 6d47ab1240f869c2b82ff2f9e691bebb566b59e8 Mon Sep 17 00:00:00 2001 From: Kez Date: Sun, 4 May 2025 14:15:39 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=ED=8C=9D=EC=97=85=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/hmh/hamyeonham/StartActivity.kt | 14 +++----------- app/src/main/res/layout/activity_sample.xml | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt index de6c6e726..d83c608b8 100644 --- a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt +++ b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import com.hmh.hamyeonham.common.dialog.OneButtonCommonDialog import com.hmh.hamyeonham.common.view.viewBinding import com.hmh.hamyeonham.databinding.ActivitySampleBinding import com.hmh.hamyeonham.feature.login.LoginActivity @@ -27,18 +26,11 @@ class StartActivity : AppCompatActivity() { private fun initLottieSplash() { binding.splashLottieAppLogo.playAnimation() - OneButtonCommonDialog - .newInstance( - title = "서비스를 개선하고 있어요", - description = "서비스를 개선하고 있어요 더 나은 모습으로 만나요", - iconRes = null, - confirmButtonText = "확인", - ) - .setConfirmButtonClickListener { + binding.splashLottieAppLogo.addAnimatorUpdateListener { + if (it.animatedFraction == 1.0f) { navigateToLogin() } - .showAllowingStateLoss(supportFragmentManager) - + } } private fun navigateToLogin() { diff --git a/app/src/main/res/layout/activity_sample.xml b/app/src/main/res/layout/activity_sample.xml index 3956b1f15..57bae3534 100644 --- a/app/src/main/res/layout/activity_sample.xml +++ b/app/src/main/res/layout/activity_sample.xml @@ -33,6 +33,6 @@ app:lottie_autoPlay="true" app:lottie_loop="false" app:lottie_rawRes="@raw/splash_logo_white" - app:lottie_speed="1" /> + app:lottie_speed="1.5" /> From 11c7abd3da596b617c5adc2ab0b6eb47fc8d3da5 Mon Sep 17 00:00:00 2001 From: Kez Date: Sun, 4 May 2025 20:24:15 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hamyeonham/core/network/di/AuthModule.kt | 19 ------ .../core/network/login/AuthService.kt | 37 ----------- .../core/network/login/model/LoginRequest.kt | 10 --- .../core/network/login/model/LoginResponse.kt | 21 ------- .../network/signup/model/SignUpRequest.kt | 61 ------------------- .../network/signup/model/SignUpResponse.kt | 30 --------- .../login/datasource/AuthDataSourceFactory.kt | 13 ---- .../datasource/kakao/KakaoAuthCodeClient.kt | 3 + ...mpl.kt => KakaoSocialAuthDataStoreImpl.kt} | 12 ++-- .../com/hmh/hamyeonham/login/di/AuthBinder.kt | 24 ++------ .../hamyeonham/login/mapper/LoginMapper.kt | 14 ----- .../login/repository/AuthRepositoryFactory.kt | 27 -------- .../login/repository/DefaultAuthRepository.kt | 55 ----------------- .../DefaultAuthRepositorySelector.kt | 12 ---- .../login/repository/LocalAuthRepository.kt | 57 ++++++++--------- .../com/hmh/hamyeonham/login/model/User.kt | 18 ++++++ .../login/repository/AuthRepository.kt | 18 +----- .../repository/AuthRepositorySelector.kt | 7 --- .../login/repository/SocialAuthDataStore.kt | 6 +- .../hamyeonham/login/usecase/AuthUseCase.kt | 24 +++----- .../feature/login/LoginViewModel.kt | 1 - .../mypage/viewmodel/MyPageViewModel.kt | 7 +-- 22 files changed, 71 insertions(+), 405 deletions(-) delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/di/AuthModule.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/login/AuthService.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginRequest.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpRequest.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt rename data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/{KakaoAuthDataSourceImpl.kt => KakaoSocialAuthDataStoreImpl.kt} (91%) delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/mapper/LoginMapper.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/User.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt rename data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt => domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt (52%) diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/AuthModule.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/AuthModule.kt deleted file mode 100644 index ce3013de2..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/AuthModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.hmh.hamyeonham.core.network.di - -import com.hmh.hamyeonham.common.qualifier.Unsecured -import com.hmh.hamyeonham.core.network.login.AuthService -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import retrofit2.create -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object AuthModule { - @Provides - @Singleton - fun provideLoginApi(@Unsecured retrofit: Retrofit): AuthService = retrofit.create() -} diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/AuthService.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/AuthService.kt deleted file mode 100644 index 7a8e7658e..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/AuthService.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.hmh.hamyeonham.core.network.login - -import com.hmh.hamyeonham.core.network.login.model.LoginRequest -import com.hmh.hamyeonham.core.network.login.model.LoginResponse -import com.hmh.hamyeonham.core.network.model.BaseResponse -import com.hmh.hamyeonham.core.network.signup.model.SignUpRequest -import com.hmh.hamyeonham.core.network.signup.model.SignUpResponse -import retrofit2.http.Body -import retrofit2.http.DELETE -import retrofit2.http.Header -import retrofit2.http.POST - -interface AuthService { - @POST("api/v1/user/login") - suspend fun login( - @Header("Authorization") accessToken: String, - @Body request: LoginRequest, - ): BaseResponse - - @POST("api/v1/user/logout") - suspend fun logout( - @Header("Authorization") accessToken: String, - ): BaseResponse - - @DELETE("api/v1/user") - suspend fun withdrawal( - @Header("Authorization") accessToken: String, - ): BaseResponse - - @POST("api/v2/user/signup") - suspend fun signUp( - @Header("Authorization") accessToken: String, - @Header("OS") OS: String, - @Header("Time-Zone") timeZone: String, - @Body request: SignUpRequest, - ): BaseResponse -} diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginRequest.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginRequest.kt deleted file mode 100644 index 43c9d81c9..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginRequest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.hmh.hamyeonham.core.network.login.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class LoginRequest( - @SerialName("socialPlatform") - val socialPlatform: String, -) diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt deleted file mode 100644 index f6d65914e..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/login/model/LoginResponse.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.hmh.hamyeonham.core.network.login.model - - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class LoginResponse( - @SerialName("token") - val token: Token? = null, - @SerialName("userId") - val userId: Long? = null -) { - @Serializable - data class Token( - @SerialName("accessToken") - val accessToken: String? = null, - @SerialName("refreshToken") - val refreshToken: String? = null - ) -} diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpRequest.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpRequest.kt deleted file mode 100644 index 42bc4ee64..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpRequest.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.hmh.hamyeonham.core.network.signup.model - -import com.hmh.hamyeonham.login.model.SignRequestDomain -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class SignUpRequest( - @SerialName("challenge") - val challenge: Challenge, - @SerialName("onboarding") - val onboarding: Onboarding, - @SerialName("socialPlatform") - val socialPlatform: String, -) { - @Serializable - data class Onboarding( - @SerialName("averageUseTime") - val averageUseTime: String, - @SerialName("problem") - val problem: List, - ) - - @Serializable - data class Challenge( - @SerialName("apps") - val app: List, - @SerialName("goalTime") - val goalTime: Long, - @SerialName("period") - val period: Int, - ) { - @Serializable - data class App( - @SerialName("appCode") - val appCode: String, - @SerialName("goalTime") - val goalTime: Long, - ) - } -} - -fun SignRequestDomain.toSignUpRequest(): SignUpRequest { - return SignUpRequest( - challenge = SignUpRequest.Challenge( - app = challenge.app.map { app -> - SignUpRequest.Challenge.App( - appCode = app.appCode, - goalTime = app.goalTime, - ) - }, - goalTime = challenge.goalTime.toLong(), - period = challenge.period, - ), - onboarding = SignUpRequest.Onboarding( - averageUseTime = onboarding.averageUseTime, - problem = onboarding.problem, - ), - socialPlatform = "KAKAO", - ) -} diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt deleted file mode 100644 index 55c649372..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/signup/model/SignUpResponse.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.hmh.hamyeonham.core.network.signup.model - -import com.hmh.hamyeonham.core.network.login.model.LoginResponse -import com.hmh.hamyeonham.login.model.SignUpUser -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class SignUpResponse( - @SerialName("token") - val token: LoginResponse.Token? = null, - @SerialName("userId") - val userId: Long, -) { - @Serializable - data class Token( - @SerialName("accessToken") - val accessToken: String? = null, - @SerialName("refreshToken") - val refreshToken: String? = null, - ) - - fun toSignUpUser(): SignUpUser { - return SignUpUser( - userId = userId, - accessToken = token?.accessToken.orEmpty(), - refreshToken = token?.refreshToken.orEmpty(), - ) - } -} diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt deleted file mode 100644 index 6f7fd9501..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSourceFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hmh.hamyeonham.login.datasource - -import com.hmh.hamyeonham.login.di.AuthProvider -import jakarta.inject.Inject -import jakarta.inject.Singleton - -@Singleton -class AuthDataSourceFactory @Inject constructor( - private val sources: Map -) { - fun get(provider: AuthProvider): AuthDataSource = - sources[provider] ?: error("No AuthDataSource bound for $provider") -} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt index 3e162b804..ab4f3cb95 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt @@ -25,6 +25,9 @@ import java.net.HttpURLConnection import java.security.MessageDigest import java.util.UUID +/** + * Kakao SDK 내부의 [AuthCodeClient] 파일 + * */ class KakaoAuthCodeClient( private val intentResolveClient: IntentResolveClient = IntentResolveClient.instance, private val applicationInfo: ApplicationInfo = KakaoSdk.applicationContextInfo, diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt similarity index 91% rename from data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt rename to data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt index 22a9b7db7..47bc9fc2b 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthDataSourceImpl.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt @@ -1,20 +1,20 @@ package com.hmh.hamyeonham.login.datasource.kakao import android.content.Context -import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.hmh.hamyeonham.login.model.User +import com.hmh.hamyeonham.login.repository.SocialAuthDataStore import com.kakao.sdk.auth.AuthApiClient import com.kakao.sdk.auth.AuthCodeClient.Companion.DEFAULT_REQUEST_CODE import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.user.UserApiClient -import com.kakao.sdk.user.model.User import dagger.hilt.android.qualifiers.ApplicationContext import jakarta.inject.Inject import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -class KakaoAuthDataSourceImpl @Inject constructor( +class KakaoSocialAuthDataStoreImpl @Inject constructor( @ApplicationContext private val context: Context -) : AuthDataSource { +) : SocialAuthDataStore { override suspend fun login(): Result = if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { @@ -35,7 +35,7 @@ class KakaoAuthDataSourceImpl @Inject constructor( loginWithAccount() } - fun loginWithKakaoTalk( + private fun loginWithKakaoTalk( context: Context, requestCode: Int = DEFAULT_REQUEST_CODE, nonce: String? = null, @@ -79,7 +79,7 @@ class KakaoAuthDataSourceImpl @Inject constructor( UserApiClient.instance.me { user, error -> when { error != null -> cont.resume(Result.failure(error)) - user != null -> cont.resume(Result.success(user)) + user != null -> cont.resume(Result.success(User(user.id))) else -> cont.resume(Result.failure(IllegalStateException("Empty Kakao user"))) } } diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt index 64ac3be25..3658ca325 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt @@ -1,14 +1,9 @@ package com.hmh.hamyeonham.login.di -import com.hmh.hamyeonham.login.repository.DefaultAuthRepository -import com.hmh.hamyeonham.login.repository.LocalAuthRepository -import com.hmh.hamyeonham.login.datasource.AuthDataSource -import com.hmh.hamyeonham.login.datasource.kakao.KakaoAuthDataSourceImpl +import com.hmh.hamyeonham.login.datasource.kakao.KakaoSocialAuthDataStoreImpl import com.hmh.hamyeonham.login.repository.AuthRepository -import com.hmh.hamyeonham.login.repository.AuthRepositorySelector -import com.hmh.hamyeonham.login.repository.DefaultAuthRepositorySelector -import com.hmh.hamyeonham.login.repository.LocalAuth -import com.hmh.hamyeonham.login.repository.RemoteAuth +import com.hmh.hamyeonham.login.repository.LocalAuthRepository +import com.hmh.hamyeonham.login.repository.SocialAuthDataStore import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -19,14 +14,9 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) interface AuthBinder { - @Binds - @Singleton - @RemoteAuth - fun bindDefaultAuthRepository(loginRepository: DefaultAuthRepository): AuthRepository @Binds @Singleton - @LocalAuth fun bindLocalAuthRepository( localAuthRepository: LocalAuthRepository ): AuthRepository @@ -35,11 +25,5 @@ interface AuthBinder { @Singleton @IntoMap @AuthDataSourceKey(AuthProvider.KAKAO) - fun bindKakaoAuthDataSource(kakaoAuthDataSourceImpl: KakaoAuthDataSourceImpl): AuthDataSource - - @Binds - @Singleton - fun bindAuthRepositorySelector( - defaultAuthRepositorySelector: DefaultAuthRepositorySelector - ): AuthRepositorySelector + fun bindKakaoSocialLoginRepositoryImpl(kakaoSocialLoginRepositoryImpl: KakaoSocialAuthDataStoreImpl): SocialAuthDataStore } diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/LoginMapper.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/LoginMapper.kt deleted file mode 100644 index b521c41cf..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/LoginMapper.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.hmh.hamyeonham.login.mapper - -import com.hmh.hamyeonham.core.network.login.model.LoginResponse -import com.hmh.hamyeonham.login.model.Login - -internal fun LoginResponse.toLogin(): Login { - return Login( - userId = userId ?: -1, - accessToken = token?.accessToken.orEmpty(), - refreshToken = token?.refreshToken.orEmpty(), - ) -} - - diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt deleted file mode 100644 index 418f73688..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositoryFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.hmh.hamyeonham.login.repository - -import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference -import com.hmh.hamyeonham.core.network.login.AuthService -import com.hmh.hamyeonham.login.datasource.AuthDataSource -import com.hmh.hamyeonham.login.di.AuthProvider -import jakarta.inject.Inject - -class AuthRepositoryFactory @Inject constructor( - private val authService: AuthService, - private val dataSourceMap: Map, - private val preference: UserPreference, - private val db: DatabaseManager -) { - fun createRemoteRepository(provider: AuthProvider): AuthRepository { - val dataSource = dataSourceMap[provider] - ?: throw IllegalArgumentException("Invalid provider") - return DefaultAuthRepository(authService, dataSource) - } - - fun createLocalRepository(provider: AuthProvider): AuthRepository { - val dataSource = dataSourceMap[provider] - ?: throw IllegalArgumentException("Invalid provider") - return LocalAuthRepository(dataSource, preference, db) - } -} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt deleted file mode 100644 index 95a7b7144..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepository.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.hmh.hamyeonham.login.repository - -import com.hmh.hamyeonham.core.network.login.AuthService -import com.hmh.hamyeonham.core.network.login.model.LoginRequest -import com.hmh.hamyeonham.core.network.signup.model.toSignUpRequest -import com.hmh.hamyeonham.login.datasource.AuthDataSource -import com.hmh.hamyeonham.login.mapper.toLogin -import com.hmh.hamyeonham.login.model.Login -import com.hmh.hamyeonham.login.model.SignRequestDomain -import com.hmh.hamyeonham.login.model.SignUpUser -import kotlinx.datetime.TimeZone - -class DefaultAuthRepository( - private val authService: AuthService, - private val authDataSource: AuthDataSource -) : AuthRepository { - - override suspend fun login(): Result { - val accessToken = authDataSource.login().getOrThrow() - val request = LoginRequest("KAKAO") // 실제 provider 이름 필요 - val bearerToken = "Bearer $accessToken" - return runCatching { - authService.login(bearerToken, request).data.toLogin() - } - } - - override suspend fun signUp( - accessToken: String, - signUpRequest: SignRequestDomain - ): Result { - val bearerToken = "Bearer $accessToken" - return runCatching { - authService.signUp( - bearerToken, - "Android", - timeZone = TimeZone.currentSystemDefault().id, - signUpRequest.toSignUpRequest(), - ).data.toSignUpUser() - } - } - - override suspend fun logout(accessToken: String): Result { - val bearerToken = "Bearer $accessToken" - return runCatching { - authService.logout(bearerToken) - } - } - - override suspend fun withdrawal(accessToken: String): Result { - val bearerToken = "Bearer $accessToken" - return runCatching { - authService.withdrawal(bearerToken) - } - } -} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt deleted file mode 100644 index 6d643bb22..000000000 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/DefaultAuthRepositorySelector.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.hmh.hamyeonham.login.repository - -import com.hmh.hamyeonham.login.di.AuthProvider -import javax.inject.Inject - -class DefaultAuthRepositorySelector @Inject constructor( - private val factory: AuthRepositoryFactory -) : AuthRepositorySelector { - override fun getRepository(provider: AuthProvider): AuthRepository { - return factory.createLocalRepository(provider) - } -} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt index 28c30c240..c16365703 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt @@ -2,51 +2,44 @@ package com.hmh.hamyeonham.login.repository import com.hmh.hamyeonham.core.database.manger.DatabaseManager import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference -import com.hmh.hamyeonham.login.datasource.AuthDataSource +import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.model.Login -import com.hmh.hamyeonham.login.model.SignRequestDomain -import com.hmh.hamyeonham.login.model.SignUpUser +import javax.inject.Inject -class LocalAuthRepository( - private val authDataSource: AuthDataSource, - private val preference: UserPreference, +class LocalAuthRepository @Inject constructor( + private val socialAuthDataStore: Map, + private val userPreference: UserPreference, private val db: DatabaseManager, ) : AuthRepository { - override suspend fun login(): Result = runCatching { - val accessToken = authDataSource.login().getOrThrow() - val kakaoUser = authDataSource.fetchUserProfile().getOrThrow() - - preference.apply { - this.accessToken = accessToken - this.userId = kakaoUser.id ?: -1 + override suspend fun login(provider: AuthProvider): Result = runCatching { + val socialAuthDataSore = getSocialAuthDataStore(provider) + val accessToke = socialAuthDataSore.login().getOrThrow() + val user = socialAuthDataSore.fetchUserProfile().getOrThrow() + userPreference.apply { + this.accessToken = accessToke + this.userId = user.id ?: -1 this.autoLoginConfigured = true } Login( - accessToken = accessToken, - refreshToken = "", - userId = kakaoUser.id ?: -1 + userId = user.id ?: -1, + accessToken = accessToke, + refreshToken = "" ) } - override suspend fun signUp( - accessToken: String, - signUpRequest: SignRequestDomain - ): Result = - Result.success( - SignUpUser( - userId = preference.userId, - accessToken = accessToken, - refreshToken = "" - ) - ) - - override suspend fun logout(accessToken: String): Result = runCatching { - authDataSource.logout().getOrThrow() - preference.clear() + override suspend fun logout(provider: AuthProvider): Result = runCatching { + val socialAuthDataStore = getSocialAuthDataStore(provider) + socialAuthDataStore.logout().getOrThrow() + userPreference.clear() db.deleteAll() } - override suspend fun withdrawal(accessToken: String): Result = logout(accessToken) + override suspend fun withdrawal(provider: AuthProvider): Result = logout(provider) + + private fun getSocialAuthDataStore(authProvider: AuthProvider): SocialAuthDataStore { + return socialAuthDataStore[authProvider] + ?: throw IllegalArgumentException("Unsupported auth provider: $authProvider") + } } \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/User.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/User.kt new file mode 100644 index 000000000..93116b708 --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/User.kt @@ -0,0 +1,18 @@ +package com.hmh.hamyeonham.login.model + +data class User( + val id: Long?, +) + + +// TODO 필요한 User 데이터 추가 -> 과연 도메인 상 유저에 어떤 값이 필요한지...? +//data class User( +// val id: Long?, +// val properties: Map?, +// val kakaoAccount: Account?, +// val groupUserToken: String?, +// val connectedAt: Date?, +// val synchedAt: Date?, +// val hasSignedUp: Boolean?, +// val uuid: String?, +//) \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt index f75d8a6d3..ab5c5d224 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt @@ -2,21 +2,9 @@ package com.hmh.hamyeonham.login.repository import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.model.Login -import com.hmh.hamyeonham.login.model.SignRequestDomain -import com.hmh.hamyeonham.login.model.SignUpUser -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class RemoteAuth - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class LocalAuth interface AuthRepository { - suspend fun login(): Result - suspend fun logout(accessToken: String): Result - suspend fun signUp(accessToken: String, signUpRequest: SignRequestDomain): Result - suspend fun withdrawal(accessToken: String): Result + suspend fun login(provider: AuthProvider): Result + suspend fun logout(provider: AuthProvider): Result + suspend fun withdrawal(provider: AuthProvider): Result } diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt deleted file mode 100644 index 436534a4d..000000000 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepositorySelector.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.hmh.hamyeonham.login.repository - -import com.hmh.hamyeonham.login.di.AuthProvider - -interface AuthRepositorySelector { - fun getRepository(provider: AuthProvider): AuthRepository -} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt similarity index 52% rename from data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt rename to domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt index 041b6a0b4..3036dc44c 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/AuthDataSource.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt @@ -1,8 +1,8 @@ -package com.hmh.hamyeonham.login.datasource +package com.hmh.hamyeonham.login.repository -import com.kakao.sdk.user.model.User +import com.hmh.hamyeonham.login.model.User -interface AuthDataSource { +interface SocialAuthDataStore { suspend fun login(): Result suspend fun fetchUserProfile(): Result suspend fun logout(): Result diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt index 572c333b9..b19c29306 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt @@ -2,31 +2,21 @@ package com.hmh.hamyeonham.login.usecase import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.model.Login -import com.hmh.hamyeonham.login.model.SignRequestDomain -import com.hmh.hamyeonham.login.model.SignUpUser -import com.hmh.hamyeonham.login.repository.AuthRepositorySelector +import com.hmh.hamyeonham.login.repository.AuthRepository import javax.inject.Inject class AuthUseCase @Inject constructor( - private val selector: AuthRepositorySelector + private val authRepository: AuthRepository, ) { suspend fun login(provider: AuthProvider): Result { - return selector.getRepository(provider).login() + return authRepository.login(provider) } - suspend fun logout(accessToken: String, provider: AuthProvider): Result { - return selector.getRepository(provider).logout(accessToken) + suspend fun logout(provider: AuthProvider): Result { + return authRepository.logout(provider) } - suspend fun signUp( - accessToken: String, - provider: AuthProvider, - request: SignRequestDomain - ): Result { - return selector.getRepository(provider).signUp(accessToken, request) - } - - suspend fun withdrawal(accessToken: String, provider: AuthProvider): Result { - return selector.getRepository(provider).withdrawal(accessToken) + suspend fun withdrawal(provider: AuthProvider): Result { + return authRepository.withdrawal(provider) } } diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt index 1c88c4375..f961e154d 100644 --- a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt +++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt @@ -56,7 +56,6 @@ class LoginViewModel @Inject constructor( _kakaoLoginEvent.emit(LoginEffect.LoginSuccess) AmplitudeUtils.trackEventWithProperties("click_onboarding_kakao") }.onFailure { - android.util.Log.e("LoginViewModel","loginWithKakaoApp failed", it) _kakaoLoginEvent.emit(LoginEffect.LoginFail) } } diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt index 558c80e96..4d5cf5990 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt @@ -1,12 +1,10 @@ package com.hmh.hamyeonham.mypage.viewmodel -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.core.database.manger.DatabaseManager import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultUserPreference import com.hmh.hamyeonham.login.di.AuthProvider -import com.hmh.hamyeonham.login.repository.AuthRepository import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -35,7 +33,7 @@ class MyPageViewModel @Inject constructor( fun handleLogout() { viewModelScope.launch { - authUseCase.logout(hmhPreference.accessToken, AuthProvider.KAKAO).onSuccess { + authUseCase.logout(AuthProvider.KAKAO).onSuccess { deleteAllDatabase() clearPreference() _userEffect.emit(UserEffect.LogoutSuccess) @@ -47,11 +45,10 @@ class MyPageViewModel @Inject constructor( fun handleWithdrawal() { viewModelScope.launch { - authUseCase.withdrawal(hmhPreference.accessToken, AuthProvider.KAKAO).onSuccess { + authUseCase.withdrawal(AuthProvider.KAKAO).onSuccess { deleteAllDatabase() clearPreference() _userEffect.emit(UserEffect.WithdrawalSuccess) - Log.d("hmhPreferenceAccessToken", hmhPreference.accessToken) }.onFailure { _userEffect.emit(UserEffect.WithdrawalFail) } From d033352348a9aacb75847144e8a1f596b0c6e7cd Mon Sep 17 00:00:00 2001 From: Kez Date: Sun, 4 May 2025 21:30:35 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20DB=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/database/HMHRoomDatabase.kt | 54 +++++++- .../core/database/dao/UserAuthDao.kt | 26 ++++ .../core/database/dao/UserProfileDao.kt | 23 ++++ .../hamyeonham/core/database/di/DaoModule.kt | 12 ++ .../core/database/di/DatabaseModule.kt | 15 ++- .../core/database/entity/UserAuth.kt | 18 +++ .../core/database/entity/UserProfile.kt | 19 +++ .../datasource/kakao/KakaoAuthCodeClient.kt | 2 +- .../kakao/KakaoSocialAuthDataStoreImpl.kt | 127 ++++++++++-------- .../login/mapper/UserProfileMapper.kt | 47 +++++++ .../login/repository/LocalAuthRepository.kt | 118 ++++++++++++---- domain/login/build.gradle.kts | 1 + .../hmh/hamyeonham/login/model/LoginInfo.kt | 13 ++ .../hmh/hamyeonham/login/model/UserInfo.kt | 21 +++ .../hmh/hamyeonham/login/model/UserProfile.kt | 13 ++ .../login/repository/AuthRepository.kt | 49 ++++++- .../login/repository/SocialAuthDataStore.kt | 32 ++++- .../hamyeonham/login/usecase/AuthUseCase.kt | 63 ++++++++- .../hamyeonham/feature/login/LoginActivity.kt | 26 ++-- .../feature/login/LoginViewModel.kt | 43 +++--- .../hmh/hamyeonham/mypage/MyPageFragment.kt | 12 +- .../mypage/viewmodel/MyPageViewModel.kt | 55 +++----- .../mypage/src/main/res/values/strings.xml | 9 ++ 23 files changed, 617 insertions(+), 181 deletions(-) create mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt create mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt create mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt create mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt create mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/mapper/UserProfileMapper.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/LoginInfo.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserInfo.kt create mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserProfile.kt diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt index 176471b3d..dd294e3c5 100644 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt @@ -2,11 +2,17 @@ package com.hmh.hamyeonham.core.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.hmh.hamyeonham.core.database.dao.ChallengeDao import com.hmh.hamyeonham.core.database.dao.DeletedGoalsDao import com.hmh.hamyeonham.core.database.dao.LockDao import com.hmh.hamyeonham.core.database.dao.UsageGoalsDao import com.hmh.hamyeonham.core.database.dao.UsageTotalGoalDao +import com.hmh.hamyeonham.core.database.dao.UserAuthDao +import com.hmh.hamyeonham.core.database.dao.UserProfileDao +import com.hmh.hamyeonham.core.database.entity.UserAuth +import com.hmh.hamyeonham.core.database.entity.UserProfile import com.hmh.hamyeonham.core.database.model.DailyChallengeEntity import com.hmh.hamyeonham.core.database.model.DeletedGoalWithUsageEntity import com.hmh.hamyeonham.core.database.model.DeletedUsageEntity @@ -26,9 +32,11 @@ import kotlinx.coroutines.launch DailyChallengeEntity::class, DeletedGoalWithUsageEntity::class, DeletedUsageEntity::class, - LockWithDateEntity::class + LockWithDateEntity::class, + UserAuth::class, + UserProfile::class ], - version = 1, + version = 2, exportSchema = false ) abstract class HMHRoomDatabase : RoomDatabase() { @@ -37,6 +45,8 @@ abstract class HMHRoomDatabase : RoomDatabase() { abstract fun challengeDao(): ChallengeDao abstract fun deletedGoalsDao(): DeletedGoalsDao abstract fun lockDao(): LockDao + abstract fun userAuthDao(): UserAuthDao + abstract fun userProfileDao(): UserProfileDao @OptIn(DelicateCoroutinesApi::class) fun deleteAll() { @@ -46,7 +56,45 @@ abstract class HMHRoomDatabase : RoomDatabase() { challengeDao().deleteAll() deletedGoalsDao().deleteAll() lockDao().deleteAll() + userAuthDao().clearUserAuth() + userProfileDao().clearUserProfile() + } + } + + companion object { + // 버전 1에서 버전 2로 마이그레이션 + val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + // UserAuth 테이블 생성 + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `user_auth` ( + `id` INTEGER NOT NULL, + `userId` INTEGER NOT NULL, + `providerType` TEXT NOT NULL, + `isLoggedIn` INTEGER NOT NULL, + `lastLoginTimestamp` INTEGER NOT NULL, + PRIMARY KEY(`id`) + ) + """ + ) + + // UserProfile 테이블 생성 + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `user_profile` ( + `userId` INTEGER NOT NULL, + `nickname` TEXT, + `profileImageUrl` TEXT, + `email` TEXT, + `ageRange` TEXT, + `gender` TEXT, + `updatedAt` INTEGER NOT NULL, + PRIMARY KEY(`userId`) + ) + """ + ) + } } - } } diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt new file mode 100644 index 000000000..ea540f51e --- /dev/null +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt @@ -0,0 +1,26 @@ +package com.hmh.hamyeonham.core.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.hmh.hamyeonham.core.database.entity.UserAuth +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserAuthDao { + @Query("SELECT * FROM user_auth WHERE id = 1") + suspend fun getUserAuth(): UserAuth? + + @Query("SELECT * FROM user_auth WHERE id = 1") + fun observeUserAuth(): Flow + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertOrUpdateUserAuth(userAuth: UserAuth) + + @Query("UPDATE user_auth SET isLoggedIn = :isLoggedIn WHERE id = 1") + suspend fun updateLoginStatus(isLoggedIn: Boolean) + + @Query("DELETE FROM user_auth") + suspend fun clearUserAuth() +} \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt new file mode 100644 index 000000000..24f4c3777 --- /dev/null +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt @@ -0,0 +1,23 @@ +package com.hmh.hamyeonham.core.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.hmh.hamyeonham.core.database.entity.UserProfile +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserProfileDao { + @Query("SELECT * FROM user_profile WHERE userId = :userId") + suspend fun getUserProfile(userId: Long): UserProfile? + + @Query("SELECT * FROM user_profile WHERE userId = :userId") + fun observeUserProfile(userId: Long): Flow + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertOrUpdateUserProfile(userProfile: UserProfile) + + @Query("DELETE FROM user_profile") + suspend fun clearUserProfile() +} \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt index 40bf7e28b..430855157 100644 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt @@ -39,4 +39,16 @@ object DaoModule { fun providesLockDao( database: HMHRoomDatabase, ) = database.lockDao() + + @Provides + @Singleton + fun providesUserAuthDao( + database: HMHRoomDatabase, + ) = database.userAuthDao() + + @Provides + @Singleton + fun providesUserProfileDao( + database: HMHRoomDatabase, + ) = database.userProfileDao() } diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DatabaseModule.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DatabaseModule.kt index a6c7e4006..6138d35d3 100644 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DatabaseModule.kt +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DatabaseModule.kt @@ -2,6 +2,7 @@ package com.hmh.hamyeonham.core.database.di import android.content.Context import androidx.room.Room +import com.hmh.hamyeonham.core.database.BuildConfig import com.hmh.hamyeonham.core.database.HMHRoomDatabase import dagger.Module import dagger.Provides @@ -17,9 +18,13 @@ object DatabaseModule { @Singleton fun providesHMHDatabase( @ApplicationContext context: Context, - ): HMHRoomDatabase = Room.databaseBuilder( - context, - HMHRoomDatabase::class.java, - "hmh-android-database", - ).build() + ): HMHRoomDatabase { + val builder = Room.databaseBuilder( + context, + HMHRoomDatabase::class.java, + "hmh-android-database" + ) + builder.addMigrations(HMHRoomDatabase.MIGRATION_1_2) + return builder.build() + } } diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt new file mode 100644 index 000000000..12394e269 --- /dev/null +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt @@ -0,0 +1,18 @@ +package com.hmh.hamyeonham.core.database.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * 사용자 인증 정보를 저장하는 엔티티 + * 토큰은 SDK에서 관리하므로 저장하지 않음 + */ +@Entity(tableName = "user_auth") +data class UserAuth( + @PrimaryKey + val id: Int = 1, // 항상 같은 ID 사용 (싱글 인스턴스) + val userId: Long = -1, + val providerType: String = "", // "KAKAO", "GOOGLE" 등 AuthProvider의 name + val isLoggedIn: Boolean = false, + val lastLoginTimestamp: Long = 0 +) \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt new file mode 100644 index 000000000..6771b3d93 --- /dev/null +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt @@ -0,0 +1,19 @@ +package com.hmh.hamyeonham.core.database.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * 사용자 프로필 정보를 저장하는 엔티티 + */ +@Entity(tableName = "user_profile") +data class UserProfile( + @PrimaryKey + val userId: Long, + val nickname: String? = null, + val profileImageUrl: String? = null, + val email: String? = null, + val ageRange: String? = null, + val gender: String? = null, + val updatedAt: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt index ab4f3cb95..eee23275a 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt @@ -34,7 +34,7 @@ class KakaoAuthCodeClient( private val contextInfo: ContextInfo = KakaoSdk.applicationContextInfo, private val approvalType: ApprovalType = KakaoSdk.approvalType, ) { - fun isKakaoTalkLoginAvailable(context: Context): Boolean = + private fun isKakaoTalkLoginAvailable(context: Context): Boolean = intentResolveClient.resolveTalkIntent(context, IntentFactory.talkBase()) != null @JvmOverloads diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt index 47bc9fc2b..86ce4958c 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt @@ -1,95 +1,110 @@ package com.hmh.hamyeonham.login.datasource.kakao import android.content.Context -import com.hmh.hamyeonham.login.model.User +import com.hmh.hamyeonham.login.model.UserInfo import com.hmh.hamyeonham.login.repository.SocialAuthDataStore import com.kakao.sdk.auth.AuthApiClient import com.kakao.sdk.auth.AuthCodeClient.Companion.DEFAULT_REQUEST_CODE -import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.user.UserApiClient import dagger.hilt.android.qualifiers.ApplicationContext -import jakarta.inject.Inject import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import javax.inject.Singleton import kotlin.coroutines.resume +@Singleton class KakaoSocialAuthDataStoreImpl @Inject constructor( @ApplicationContext private val context: Context ) : SocialAuthDataStore { - override suspend fun login(): Result = - if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { - suspendCancellableCoroutine { cont -> - loginWithKakaoTalk(context) { token, error -> - when { - error != null -> cont.resume(Result.failure(error)) - token != null -> cont.resume(Result.success(token.accessToken)) - else -> cont.resume( - Result.failure( - IllegalStateException("Empty KakaoTalk token") - ) - ) - } + override suspend fun login(): UserInfo? { + return try { + // 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 계정으로 로그인 + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + loginWithKakaoTalk() + } else { + loginWithAccount() + } + + // 로그인 성공 후 사용자 정보 조회 + fetchUserInfo() + } catch (e: Exception) { + null + } + } + + override suspend fun isLoggedIn(): Boolean { + return AuthApiClient.instance.hasToken() + } + + override suspend fun logout() { + return suspendCancellableCoroutine { continuation -> + UserApiClient.instance.logout { error -> + if (error != null) { + continuation.resume(Unit) + } else { + continuation.resume(Unit) } } - } else { - loginWithAccount() } + } - private fun loginWithKakaoTalk( - context: Context, - requestCode: Int = DEFAULT_REQUEST_CODE, - nonce: String? = null, - channelPublicIds: List? = null, - serviceTerms: List? = null, - callback: (token: OAuthToken?, error: Throwable?) -> Unit, - ) { + override suspend fun refreshUserInfo(): UserInfo? { + return try { + fetchUserInfo() + } catch (e: Exception) { + null + } + } + + private suspend fun loginWithKakaoTalk(): Unit = suspendCancellableCoroutine { continuation -> val codeVerifier = KakaoAuthCodeClient.codeVerifier() KakaoAuthCodeClient.instance.authorizeWithKakaoTalk( context, prompts = null, - requestCode, - nonce = nonce, - channelPublicIds = channelPublicIds, - serviceTerms = serviceTerms, + DEFAULT_REQUEST_CODE, + nonce = null, + channelPublicIds = null, + serviceTerms = null, codeVerifier = codeVerifier ) { code, codeError -> if (codeError != null) { - callback(null, codeError) + continuation.resume(Unit) } else { - AuthApiClient.instance.issueAccessToken(code!!, codeVerifier) { token, tokenError -> - callback(token, tokenError) + AuthApiClient.instance.issueAccessToken(code!!, codeVerifier) { _, tokenError -> + continuation.resume(Unit) } } } } - - private suspend fun loginWithAccount(): Result = suspendCancellableCoroutine { cont -> - UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> - when { - error != null -> cont.resume(Result.failure(error)) - token != null -> cont.resume(Result.success(token.accessToken)) - else -> cont.resume(Result.failure(IllegalStateException("Empty KakaoAccount token"))) - } + private suspend fun loginWithAccount(): Unit = suspendCancellableCoroutine { continuation -> + UserApiClient.instance.loginWithKakaoAccount(context) { _, error -> + continuation.resume(Unit) } } - override suspend fun fetchUserProfile(): Result = - suspendCancellableCoroutine { cont -> - UserApiClient.instance.me { user, error -> - when { - error != null -> cont.resume(Result.failure(error)) - user != null -> cont.resume(Result.success(User(user.id))) - else -> cont.resume(Result.failure(IllegalStateException("Empty Kakao user"))) - } + private suspend fun fetchUserInfo(): UserInfo? = suspendCancellableCoroutine { continuation -> + UserApiClient.instance.me { user, error -> + if (error != null || user == null) { + continuation.resume(null) + return@me } - } - override suspend fun logout(): Result = - suspendCancellableCoroutine { cont -> - UserApiClient.instance.logout { error -> - if (error != null) cont.resume(Result.failure(error)) - else cont.resume(Result.success(Unit)) - } + val profile = user.kakaoAccount?.profile + val userInfo = UserInfo( + id = user.id ?: -1, + profile = UserInfo.Profile( + userId = user.id ?: -1, + nickname = profile?.nickname, + profileImageUrl = profile?.profileImageUrl, + email = user.kakaoAccount?.email, + ageRange = user.kakaoAccount?.ageRange?.name, + gender = user.kakaoAccount?.gender?.name + ) + ) + + continuation.resume(userInfo) } + } } \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/UserProfileMapper.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/UserProfileMapper.kt new file mode 100644 index 000000000..826dd8d2d --- /dev/null +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/mapper/UserProfileMapper.kt @@ -0,0 +1,47 @@ +package com.hmh.hamyeonham.login.mapper + +import com.hmh.hamyeonham.core.database.entity.UserProfile as DbUserProfile +import com.hmh.hamyeonham.login.model.UserInfo +import com.hmh.hamyeonham.login.model.UserProfile + +/** + * 데이터베이스 엔티티 UserProfile을 도메인 모델 UserProfile로 변환 + */ +fun DbUserProfile.toDomain(): UserProfile { + return UserProfile( + userId = this.userId, + nickname = this.nickname, + profileImageUrl = this.profileImageUrl, + email = this.email, + ageRange = this.ageRange, + gender = this.gender + ) +} + +/** + * 도메인 모델 UserProfile을 데이터베이스 엔티티 UserProfile로 변환 + */ +fun UserProfile.toDbEntity(): DbUserProfile { + return DbUserProfile( + userId = this.userId, + nickname = this.nickname, + profileImageUrl = this.profileImageUrl, + email = this.email, + ageRange = this.ageRange, + gender = this.gender + ) +} + +/** + * 소셜 로그인 후 받은 UserInfo.Profile을 데이터베이스 엔티티 UserProfile로 변환 + */ +fun UserInfo.Profile.toDbEntity(): DbUserProfile { + return DbUserProfile( + userId = this.userId, + nickname = this.nickname, + profileImageUrl = this.profileImageUrl, + email = this.email, + ageRange = this.ageRange, + gender = this.gender + ) +} \ No newline at end of file diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt index c16365703..8d68190a4 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt @@ -1,45 +1,109 @@ package com.hmh.hamyeonham.login.repository -import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference +import com.hmh.hamyeonham.core.database.dao.UserAuthDao +import com.hmh.hamyeonham.core.database.dao.UserProfileDao +import com.hmh.hamyeonham.core.database.entity.UserAuth import com.hmh.hamyeonham.login.di.AuthProvider -import com.hmh.hamyeonham.login.model.Login +import com.hmh.hamyeonham.login.mapper.toDbEntity +import com.hmh.hamyeonham.login.mapper.toDomain +import com.hmh.hamyeonham.login.model.LoginInfo +import com.hmh.hamyeonham.login.model.UserProfile +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject +import javax.inject.Singleton +@Singleton class LocalAuthRepository @Inject constructor( - private val socialAuthDataStore: Map, - private val userPreference: UserPreference, - private val db: DatabaseManager, + private val userAuthDao: UserAuthDao, + private val userProfileDao: UserProfileDao, + private val socialAuthDataStores: Map ) : AuthRepository { - override suspend fun login(provider: AuthProvider): Result = runCatching { - val socialAuthDataSore = getSocialAuthDataStore(provider) - val accessToke = socialAuthDataSore.login().getOrThrow() - val user = socialAuthDataSore.fetchUserProfile().getOrThrow() - userPreference.apply { - this.accessToken = accessToke - this.userId = user.id ?: -1 - this.autoLoginConfigured = true + override suspend fun getCurrentAuth(): Result = runCatching { + val userAuth = userAuthDao.getUserAuth() ?: return@runCatching null + if (!userAuth.isLoggedIn) return@runCatching null + + val provider = AuthProvider.valueOf(userAuth.providerType) + LoginInfo( + userId = userAuth.userId, + provider = provider, + isLoggedIn = true, + lastLoginTimestamp = userAuth.lastLoginTimestamp + ) + } + + override suspend fun login(provider: AuthProvider): Result = runCatching { + val dataStore = socialAuthDataStores[provider] + ?: throw IllegalArgumentException("지원하지 않는 소셜 로그인 제공자입니다: $provider") + + // SDK를 사용한 소셜 로그인 처리 + val userInfo = dataStore.login() + ?: throw IllegalStateException("소셜 로그인에 실패했습니다") + + // 로그인 정보 DB에 저장 + val userAuth = UserAuth( + userId = userInfo.id, + providerType = provider.name, + isLoggedIn = true, + lastLoginTimestamp = System.currentTimeMillis() + ) + userAuthDao.insertOrUpdateUserAuth(userAuth) + + // 사용자 프로필 정보 DB에 저장 + val userProfile = userInfo.profile?.toDbEntity() + if (userProfile != null) { + userProfileDao.insertOrUpdateUserProfile(userProfile) } - Login( - userId = user.id ?: -1, - accessToken = accessToke, - refreshToken = "" + // 로그인 정보 반환 + LoginInfo( + userId = userAuth.userId, + provider = provider, + isLoggedIn = true, + lastLoginTimestamp = userAuth.lastLoginTimestamp ) } - override suspend fun logout(provider: AuthProvider): Result = runCatching { - val socialAuthDataStore = getSocialAuthDataStore(provider) - socialAuthDataStore.logout().getOrThrow() - userPreference.clear() - db.deleteAll() + override suspend fun logout(): Result = runCatching { + val userAuth = userAuthDao.getUserAuth() ?: return@runCatching Unit // 이미 로그아웃 상태 + + // 현재 로그인된 제공자 확인 + val provider = AuthProvider.valueOf(userAuth.providerType) + val dataStore = socialAuthDataStores[provider] + ?: throw IllegalArgumentException("지원하지 않는 소셜 로그인 제공자입니다: $provider") + + // SDK를 사용한 소셜 로그아웃 처리 + dataStore.logout() + + // DB에서 로그인 상태 업데이트 + userAuthDao.updateLoginStatus(false) + } + + override suspend fun isLoggedIn(): Result = runCatching { + val userAuth = userAuthDao.getUserAuth() ?: return@runCatching false + userAuth.isLoggedIn } - override suspend fun withdrawal(provider: AuthProvider): Result = logout(provider) + override fun observeLoginStatus(): Flow { + return userAuthDao.observeUserAuth().map { userAuth -> + userAuth?.isLoggedIn ?: false + } + } - private fun getSocialAuthDataStore(authProvider: AuthProvider): SocialAuthDataStore { - return socialAuthDataStore[authProvider] - ?: throw IllegalArgumentException("Unsupported auth provider: $authProvider") + override suspend fun getUserProfile(): Result = runCatching { + val userAuth = userAuthDao.getUserAuth() ?: return@runCatching null + if (!userAuth.isLoggedIn) return@runCatching null + + val dbProfile = userProfileDao.getUserProfile(userAuth.userId) ?: return@runCatching null + dbProfile.toDomain() + } + + override fun observeUserProfile(): Flow { + return userAuthDao.observeUserAuth().map { userAuth -> + if (userAuth == null || !userAuth.isLoggedIn) return@map null + val profile = userProfileDao.getUserProfile(userAuth.userId) ?: return@map null + profile.toDomain() + } } } \ No newline at end of file diff --git a/domain/login/build.gradle.kts b/domain/login/build.gradle.kts index ed7223500..05f500202 100644 --- a/domain/login/build.gradle.kts +++ b/domain/login/build.gradle.kts @@ -6,4 +6,5 @@ plugins { dependencies { implementation(libs.javax.inject) + implementation(libs.kotlin.coroutines) } diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/LoginInfo.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/LoginInfo.kt new file mode 100644 index 000000000..75dca4b0e --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/LoginInfo.kt @@ -0,0 +1,13 @@ +package com.hmh.hamyeonham.login.model + +import com.hmh.hamyeonham.login.di.AuthProvider + +/** + * 사용자 로그인 정보를 담는 모델 클래스 + */ +data class LoginInfo( + val userId: Long, + val provider: AuthProvider, + val isLoggedIn: Boolean, + val lastLoginTimestamp: Long +) \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserInfo.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserInfo.kt new file mode 100644 index 000000000..a3b8c9c36 --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserInfo.kt @@ -0,0 +1,21 @@ +package com.hmh.hamyeonham.login.model + +/** + * 소셜 로그인으로부터 받은 사용자 정보 + */ +data class UserInfo( + val id: Long, + val profile: Profile? = null +) { + /** + * 사용자 프로필 정보 + */ + data class Profile( + val userId: Long, + val nickname: String? = null, + val profileImageUrl: String? = null, + val email: String? = null, + val ageRange: String? = null, + val gender: String? = null + ) +} \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserProfile.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserProfile.kt new file mode 100644 index 000000000..a4ce162c9 --- /dev/null +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserProfile.kt @@ -0,0 +1,13 @@ +package com.hmh.hamyeonham.login.model + +/** + * 사용자 프로필 정보를 담는 모델 클래스 + */ +data class UserProfile( + val userId: Long, + val nickname: String? = null, + val profileImageUrl: String? = null, + val email: String? = null, + val ageRange: String? = null, + val gender: String? = null +) \ No newline at end of file diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt index ab5c5d224..88b6166b0 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt @@ -1,10 +1,51 @@ package com.hmh.hamyeonham.login.repository import com.hmh.hamyeonham.login.di.AuthProvider -import com.hmh.hamyeonham.login.model.Login +import com.hmh.hamyeonham.login.model.LoginInfo +import com.hmh.hamyeonham.login.model.UserProfile +import kotlinx.coroutines.flow.Flow interface AuthRepository { - suspend fun login(provider: AuthProvider): Result - suspend fun logout(provider: AuthProvider): Result - suspend fun withdrawal(provider: AuthProvider): Result + /** + * 현재 로그인된 사용자의 인증 정보 가져오기 + * @return 현재 로그인 제공자 및 정보 + */ + suspend fun getCurrentAuth(): Result + + /** + * 사용자 로그인 처리 + * @param provider 로그인 제공자 (KAKAO 등) + * @return 로그인 결과 (성공 또는 실패 원인) + */ + suspend fun login(provider: AuthProvider): Result + + /** + * 사용자 로그아웃 처리 + * @return 로그아웃 결과 (성공 또는 실패 원인) + */ + suspend fun logout(): Result + + /** + * 현재 로그인 상태 확인 + * @return 로그인 상태 결과 + */ + suspend fun isLoggedIn(): Result + + /** + * 로그인 상태를 Flow로 관찰 + * @return 로그인 상태 Flow + */ + fun observeLoginStatus(): Flow + + /** + * 현재 로그인된 사용자의 프로필 정보 + * @return 사용자 프로필 데이터 결과 + */ + suspend fun getUserProfile(): Result + + /** + * 사용자 프로필 정보를 Flow로 관찰 + * @return 사용자 프로필 Flow + */ + fun observeUserProfile(): Flow } diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt index 3036dc44c..99ceb3a36 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt @@ -1,9 +1,33 @@ package com.hmh.hamyeonham.login.repository -import com.hmh.hamyeonham.login.model.User +import com.hmh.hamyeonham.login.model.UserInfo +/** + * 소셜 로그인 구현체에 대한 인터페이스 + * 각 소셜 로그인 구현체(카카오, 구글 등)는 이 인터페이스를 구현해야 함 + */ interface SocialAuthDataStore { - suspend fun login(): Result - suspend fun fetchUserProfile(): Result - suspend fun logout(): Result + /** + * 소셜 로그인을 수행하고 사용자 정보를 반환 + * 토큰은 SDK에서 자체 관리하므로 반환하지 않음 + * @return 로그인 성공 시 사용자 정보, 실패 시 null + */ + suspend fun login(): UserInfo? + + /** + * 현재 로그인 상태인지 확인 + * @return 로그인 상태면 true, 아니면 false + */ + suspend fun isLoggedIn(): Boolean + + /** + * 소셜 로그아웃 처리 + */ + suspend fun logout() + + /** + * 프로필 정보 갱신 + * @return 최신 사용자 정보 + */ + suspend fun refreshUserInfo(): UserInfo? } diff --git a/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt index b19c29306..00ea25381 100644 --- a/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt +++ b/domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt @@ -1,22 +1,73 @@ package com.hmh.hamyeonham.login.usecase import com.hmh.hamyeonham.login.di.AuthProvider -import com.hmh.hamyeonham.login.model.Login +import com.hmh.hamyeonham.login.model.LoginInfo +import com.hmh.hamyeonham.login.model.UserProfile import com.hmh.hamyeonham.login.repository.AuthRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject +import javax.inject.Singleton +@Singleton class AuthUseCase @Inject constructor( private val authRepository: AuthRepository, ) { - suspend fun login(provider: AuthProvider): Result { + /** + * 현재 로그인된 사용자의 인증 정보 가져오기 + */ + suspend fun getCurrentAuth(): Result { + return authRepository.getCurrentAuth() + } + + /** + * 소셜 로그인 수행 + */ + suspend fun login(provider: AuthProvider): Result { return authRepository.login(provider) } - suspend fun logout(provider: AuthProvider): Result { - return authRepository.logout(provider) + /** + * 로그아웃 수행 + */ + suspend fun logout(): Result { + return authRepository.logout() + } + + /** + * 회원 탈퇴 처리 + * 로그아웃 후 사용자 데이터 모두 삭제 + */ + suspend fun withdrawal(): Result { + // 회원 탈퇴는 현재 별도 프로세스가 없어 로그아웃과 동일하게 처리함 + // 추후 서버 API 연동 필요시 여기에 구현 + return authRepository.logout() + } + + /** + * 현재 로그인 상태 확인 + */ + suspend fun isLoggedIn(): Result { + return authRepository.isLoggedIn() + } + + /** + * 사용자 프로필 정보 가져오기 + */ + suspend fun getUserProfile(): Result { + return authRepository.getUserProfile() + } + + /** + * 로그인 상태를 Flow로 관찰 + */ + fun observeLoginStatus(): Flow { + return authRepository.observeLoginStatus() } - suspend fun withdrawal(provider: AuthProvider): Result { - return authRepository.withdrawal(provider) + /** + * 사용자 프로필 정보를 Flow로 관찰 + */ + fun observeUserProfile(): Flow { + return authRepository.observeUserProfile() } } diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt index 860c14201..91461ca5a 100644 --- a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt +++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt @@ -34,27 +34,27 @@ class LoginActivity : AppCompatActivity() { setContentView(binding.root) binding.ivKakaoLogin.setOnClickListener { - viewModel.loginWithKakaoApp() + viewModel.loginWithKakao() } setLoginViewPager() - handleKakaoLoginSuccess() - handleAutoLoginSuccess() + handleLoginEffect() + checkLoginState() } - private fun handleAutoLoginSuccess() { - viewModel.loginState.flowWithLifecycle(lifecycle).onEach { state -> - if (state.autoLogin) { + private fun checkLoginState() { + viewModel.loginState.flowWithLifecycle(lifecycle).onEach { loginState -> + if (loginState.autoLogin) { navigateToMainActivity() } }.launchIn(lifecycleScope) } - private fun handleKakaoLoginSuccess() { - viewModel.kakaoLoginEvent.flowWithLifecycle(lifecycle).onEach { state -> - when (state) { + private fun handleLoginEffect() { + viewModel.effect.flowWithLifecycle(lifecycle).onEach { effect -> + when (effect) { is LoginEffect.LoginSuccess -> navigateToMainActivity() is LoginEffect.LoginFail -> toast(getString(R.string.fail_kakao_login)) - is LoginEffect.RequireSignUp -> navigateToOnBoardingActivity() + is LoginEffect.LoginError -> toast(effect.message) } }.launchIn(lifecycleScope) } @@ -88,12 +88,6 @@ class LoginActivity : AppCompatActivity() { autoScrollJob.cancel() } - private fun navigateToOnBoardingActivity() { - val intent = navigationProvider.toOnBoarding() - startActivity(intent) - finish() - } - private fun navigateToMainActivity() { startActivity(navigationProvider.toMain()) finish() diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt index f961e154d..4fe8e1816 100644 --- a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt +++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt @@ -3,7 +3,6 @@ package com.hmh.hamyeonham.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils -import com.hmh.hamyeonham.core.network.auth.datastore.network.UserPreference import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel @@ -19,44 +18,52 @@ sealed interface LoginEffect { data object LoginFail : LoginEffect - data object RequireSignUp : LoginEffect + data class LoginError(val message: String) : LoginEffect } data class LoginState( val autoLogin: Boolean = false, + val isLoading: Boolean = false ) @HiltViewModel class LoginViewModel @Inject constructor( private val authUseCase: AuthUseCase, - private val userPreference: UserPreference, ) : ViewModel() { - private val _kakaoLoginEvent = MutableSharedFlow() - val kakaoLoginEvent = _kakaoLoginEvent.asSharedFlow() - private val _loginState = MutableStateFlow(LoginState()) val loginState = _loginState.asStateFlow() + private val _effect = MutableSharedFlow() + val effect = _effect.asSharedFlow() + init { - updateLoginState() + viewModelScope.launch { + authUseCase.isLoggedIn().onSuccess { isLoggedIn -> + _loginState.value = loginState.value.copy( + autoLogin = isLoggedIn, + isLoading = false + ) + } + } } - private fun updateLoginState() { - val currentState = loginState.value - _loginState.value = currentState.copy( - autoLogin = userPreference.autoLoginConfigured, - ) + fun loginWithKakao() { + loginWith(AuthProvider.KAKAO) } - fun loginWithKakaoApp() { + fun loginWithGoogle() { + loginWith(AuthProvider.GOOGLE) + } + + private fun loginWith(provider: AuthProvider) { viewModelScope.launch { - authUseCase.login(AuthProvider.KAKAO) + authUseCase.login(provider) .onSuccess { - // TODO if (온보딩을 타야하는 경우) _kakaoLoginEvent.emit(LoginEffect.RequireSignUp) - _kakaoLoginEvent.emit(LoginEffect.LoginSuccess) AmplitudeUtils.trackEventWithProperties("click_onboarding_kakao") - }.onFailure { - _kakaoLoginEvent.emit(LoginEffect.LoginFail) + _effect.emit(LoginEffect.LoginSuccess) + } + .onFailure { exception -> + _effect.emit(LoginEffect.LoginError(exception.message ?: "로그인에 실패했습니다")) } } } diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt index e3eca1b77..ac48dd461 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt @@ -76,7 +76,7 @@ class MyPageFragment : Fragment() { dismissButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_cancel), ).apply { setConfirmButtonClickListener { - viewModel.handleLogout() + viewModel.logout() } }.showAllowingStateLoss(childFragmentManager) } @@ -89,19 +89,19 @@ class MyPageFragment : Fragment() { dismissButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_cancel), ).apply { setConfirmButtonClickListener { - viewModel.handleWithdrawal() + viewModel.withdrawal() } }.showAllowingStateLoss(childFragmentManager) } } private fun collectEffect() { - viewModel.userEffect.flowWithLifecycle(lifecycle).onEach { state -> - when (state) { + viewModel.effect.flowWithLifecycle(lifecycle).onEach { effect -> + when (effect) { is UserEffect.WithdrawalSuccess -> moveToLoginActivity() - is UserEffect.WithdrawalFail -> toast(getString(R.string.withdrawal_fail)) + is UserEffect.WithdrawalFail -> toast(effect.message) is UserEffect.LogoutSuccess -> moveToLoginActivity() - is UserEffect.LogoutFail -> toast(getString(R.string.logout_fail)) + is UserEffect.LogoutFail -> toast(effect.message) } }.launchIn(viewLifecycleOwner.lifecycleScope) } diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt index 4d5cf5990..dec48e6f4 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hmh.hamyeonham.core.database.manger.DatabaseManager import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultUserPreference -import com.hmh.hamyeonham.login.di.AuthProvider import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -14,54 +13,40 @@ import javax.inject.Inject sealed interface UserEffect { data object LogoutSuccess : UserEffect - data object LogoutFail : UserEffect - + data class LogoutFail(val message: String) : UserEffect data object WithdrawalSuccess : UserEffect - - data object WithdrawalFail : UserEffect + data class WithdrawalFail(val message: String) : UserEffect } @HiltViewModel class MyPageViewModel @Inject constructor( private val authUseCase: AuthUseCase, - private val hmhPreference: DefaultUserPreference, - private val databaseManager: DatabaseManager ) : ViewModel() { - private val _userEffect = MutableSharedFlow() - val userEffect = _userEffect.asSharedFlow() - - fun handleLogout() { - viewModelScope.launch { - authUseCase.logout(AuthProvider.KAKAO).onSuccess { - deleteAllDatabase() - clearPreference() - _userEffect.emit(UserEffect.LogoutSuccess) - }.onFailure { - _userEffect.emit(UserEffect.LogoutFail) - } - } - } + private val _effect = MutableSharedFlow() + val effect = _effect.asSharedFlow() - fun handleWithdrawal() { + fun logout() { viewModelScope.launch { - authUseCase.withdrawal(AuthProvider.KAKAO).onSuccess { - deleteAllDatabase() - clearPreference() - _userEffect.emit(UserEffect.WithdrawalSuccess) - }.onFailure { - _userEffect.emit(UserEffect.WithdrawalFail) - } + authUseCase.logout() + .onSuccess { + _effect.emit(UserEffect.LogoutSuccess) + } + .onFailure { exception -> + _effect.emit(UserEffect.LogoutFail(exception.message ?: "로그아웃에 실패했습니다")) + } } } - private fun clearPreference() { - hmhPreference.clear() - } - - private fun deleteAllDatabase() { + fun withdrawal() { viewModelScope.launch { - databaseManager.deleteAll() + authUseCase.withdrawal() + .onSuccess { + _effect.emit(UserEffect.WithdrawalSuccess) + } + .onFailure { exception -> + _effect.emit(UserEffect.WithdrawalFail(exception.message ?: "회원탈퇴에 실패했습니다")) + } } } } diff --git a/feature/mypage/src/main/res/values/strings.xml b/feature/mypage/src/main/res/values/strings.xml index 217cc1c9c..70141f274 100644 --- a/feature/mypage/src/main/res/values/strings.xml +++ b/feature/mypage/src/main/res/values/strings.xml @@ -26,4 +26,13 @@ 이용약관 버튼 이미지 개인정보처리방침 버튼 이미지 + 프로필 정보 + 로그인 상태: 로그인됨 + 로그인 상태: 로그아웃됨 + 이름 + 이메일 + 사용자 프로필 사진 + 로그인하면 프로필 정보를 볼 수 있습니다. + 프로필 새로고침 + From bc605e749a7c8e246bf3d0445329579f768fbb38 Mon Sep 17 00:00:00 2001 From: Kez Date: Mon, 5 May 2025 23:37:58 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Kakao=20Version=20Update=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt | 2 +- .../java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt index eee23275a..57874cda5 100644 --- a/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt +++ b/data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt @@ -166,7 +166,7 @@ class KakaoAuthCodeClient( val error = uri.getQueryParameter(Constants.ERROR) ?: Constants.UNKNOWN_ERROR val errorDescription = uri.getQueryParameter(Constants.ERROR_DESCRIPTION) val errorCause = runCatching { - KakaoJson.fromJson(error, AuthErrorCause::class.java) + KakaoJson.decodeFromString(error) }.getOrDefault(AuthErrorCause.Unknown) AuthError( diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt index dec48e6f4..c78b46271 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/viewmodel/MyPageViewModel.kt @@ -2,8 +2,6 @@ package com.hmh.hamyeonham.mypage.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.hmh.hamyeonham.core.database.manger.DatabaseManager -import com.hmh.hamyeonham.core.network.auth.datastore.network.DefaultUserPreference import com.hmh.hamyeonham.login.usecase.AuthUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow From 0ee991da0de2ec6cf326c21ae029e9280d1240c0 Mon Sep 17 00:00:00 2001 From: Kez Date: Fri, 9 May 2025 23:57:39 +0900 Subject: [PATCH 6/9] =?UTF-8?q?Point=20=EC=A0=9C=EA=B1=B0=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 - .../java/com/hmh/hamyeonham/StartActivity.kt | 3 - .../navigation/DefaultNavigationProvider.kt | 5 - .../common/navigation/NavigationProvider.kt | 1 - .../hamyeonham/core/network/di/PointModule.kt | 20 --- .../core/network/point/PointService.kt | 32 ---- .../point/model/ChallengeDateRequest.kt | 8 - .../network/point/model/EarnPointRequest.kt | 10 -- .../network/point/model/EarnPointResponse.kt | 11 -- .../network/point/model/PointListResponse.kt | 23 --- .../point/model/UsablePointResponse.kt | 11 -- .../network/point/model/UsePointResponse.kt | 13 -- core/viewmodel/main/build.gradle.kts | 1 - .../hamyeonham/core/viewmodel/MainEffect.kt | 2 - .../core/viewmodel/MainViewModel.kt | 42 +---- data/point/.gitignore | 1 - data/point/build.gradle.kts | 15 -- data/point/src/main/AndroidManifest.xml | 4 - .../hmh/hamyeonham/data/point/PointMapper.kt | 41 ----- .../hamyeonham/data/point/di/PointBinder.kt | 17 -- .../repository/DefaultPointRepository.kt | 41 ----- domain/point/.gitignore | 1 - domain/point/build.gradle.kts | 9 - domain/point/src/main/AndroidManifest.xml | 4 - .../domain/point/model/EarnPoint.kt | 6 - .../domain/point/model/PointInfo.kt | 22 --- .../domain/point/model/UsablePoint.kt | 6 - .../hamyeonham/domain/point/model/UsePoint.kt | 6 - .../point/repository/PointRepository.kt | 13 -- feature/challenge/build.gradle.kts | 1 - .../hamyeonham/challenge/ChallengeFragment.kt | 51 +----- .../challenge/point/PointActivity.kt | 79 --------- .../challenge/point/PointAdapter.kt | 163 ------------------ .../challenge/point/PointViewModel.kt | 52 ------ .../src/main/res/layout/activity_point.xml | 74 -------- .../main/res/layout/fragment_challenge.xml | 9 - .../hamyeonham/feature/main/MainActivity.kt | 47 +---- feature/main/src/main/res/values/strings.xml | 6 - .../hmh/hamyeonham/mypage/MyPageFragment.kt | 9 +- .../src/main/res/layout/fragment_my_page.xml | 50 +----- 40 files changed, 17 insertions(+), 894 deletions(-) delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/di/PointModule.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/PointService.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/ChallengeDateRequest.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointRequest.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointResponse.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/PointListResponse.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsablePointResponse.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsePointResponse.kt delete mode 100644 data/point/.gitignore delete mode 100644 data/point/build.gradle.kts delete mode 100644 data/point/src/main/AndroidManifest.xml delete mode 100644 data/point/src/main/java/com/hmh/hamyeonham/data/point/PointMapper.kt delete mode 100644 data/point/src/main/java/com/hmh/hamyeonham/data/point/di/PointBinder.kt delete mode 100644 data/point/src/main/java/com/hmh/hamyeonham/data/point/repository/DefaultPointRepository.kt delete mode 100644 domain/point/.gitignore delete mode 100644 domain/point/build.gradle.kts delete mode 100644 domain/point/src/main/AndroidManifest.xml delete mode 100644 domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/EarnPoint.kt delete mode 100644 domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/PointInfo.kt delete mode 100644 domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsablePoint.kt delete mode 100644 domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsePoint.kt delete mode 100644 domain/point/src/main/java/com/hmh/hamyeonham/domain/point/repository/PointRepository.kt delete mode 100644 feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointActivity.kt delete mode 100644 feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointAdapter.kt delete mode 100644 feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointViewModel.kt delete mode 100644 feature/challenge/src/main/res/layout/activity_point.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f9c36afab..08f844047 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,7 +76,6 @@ dependencies { implementation(projects.domain.login) implementation(projects.domain.challenge) implementation(projects.domain.onboarding) - implementation(projects.domain.point) implementation(projects.domain.lock) // Data @@ -86,7 +85,6 @@ dependencies { implementation(projects.data.challenge) implementation(projects.data.device) implementation(projects.data.onboarding) - implementation(projects.data.point) implementation(projects.data.lock) implementation(projects.data.main) diff --git a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt index d83c608b8..781f24b9a 100644 --- a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt +++ b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt @@ -8,13 +8,10 @@ import com.hmh.hamyeonham.common.view.viewBinding import com.hmh.hamyeonham.databinding.ActivitySampleBinding import com.hmh.hamyeonham.feature.login.LoginActivity import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers @AndroidEntryPoint class StartActivity : AppCompatActivity() { private val binding by viewBinding(ActivitySampleBinding::inflate) - private val coroutineScope = CoroutineScope(Dispatchers.Main) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt b/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt index 15c7b5992..84aa868a3 100644 --- a/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt +++ b/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt @@ -2,7 +2,6 @@ package com.hmh.hamyeonham.navigation import android.content.Context import android.content.Intent -import com.hmh.hamyeonham.challenge.point.PointActivity import com.hmh.hamyeonham.common.navigation.NavigationProvider import com.hmh.hamyeonham.common.permission.PermissionActivity import com.hmh.hamyeonham.feature.lock.LockActivity @@ -41,10 +40,6 @@ class DefaultNavigationProvider @Inject constructor( return Intent(context, StoreActivity::class.java) } - override fun toPoint(): Intent { - return Intent(context, PointActivity::class.java) - } - override fun toPermission(): Intent { return Intent(context, PermissionActivity::class.java) } diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt index 4ec1d86d1..3b0180bd1 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt @@ -14,6 +14,5 @@ interface NavigationProvider { fun toMain(): Intent fun toLock(packageName: String): Intent fun toStore(): Intent - fun toPoint(): Intent fun toPermission(): Intent } diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/PointModule.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/PointModule.kt deleted file mode 100644 index 0bf6f1ded..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/di/PointModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.hmh.hamyeonham.core.network.di - -import com.hmh.hamyeonham.common.qualifier.Secured -import com.hmh.hamyeonham.core.network.point.PointService -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import retrofit2.create -import javax.inject.Singleton - - -@Module -@InstallIn(SingletonComponent::class) -object PointModule { - @Provides - @Singleton - fun providePointService(@Secured retrofit: Retrofit): PointService = retrofit.create() -} \ No newline at end of file diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/PointService.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/PointService.kt deleted file mode 100644 index e061ec36d..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/PointService.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.hmh.hamyeonham.core.network.point - -import com.hmh.hamyeonham.core.network.model.BaseResponse -import com.hmh.hamyeonham.core.network.point.model.ChallengeDateRequest -import com.hmh.hamyeonham.core.network.point.model.EarnPointRequest -import com.hmh.hamyeonham.core.network.point.model.EarnPointResponse -import com.hmh.hamyeonham.core.network.point.model.PointListResponse -import com.hmh.hamyeonham.core.network.point.model.UsablePointResponse -import com.hmh.hamyeonham.core.network.point.model.UsePointResponse -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.PATCH - -interface PointService { - - @PATCH("/api/v1/point/earn") - suspend fun earnPoint( - @Body request: EarnPointRequest, - ): BaseResponse - - @GET("/api/v2/point/use") - suspend fun getUsablePoint(): UsablePointResponse - - @PATCH("/api/v1/point/use") - suspend fun patchPoint( - @Body body: ChallengeDateRequest - ): UsePointResponse - - @GET("/api/v1/point/list") - suspend fun getPointInfoList(): BaseResponse - -} \ No newline at end of file diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/ChallengeDateRequest.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/ChallengeDateRequest.kt deleted file mode 100644 index a8dc550f1..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/ChallengeDateRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - -import kotlinx.serialization.Serializable - -@Serializable -data class ChallengeDateRequest( - val challengeDate: String -) diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointRequest.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointRequest.kt deleted file mode 100644 index cba6619cb..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointRequest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class EarnPointRequest( - @SerialName("challengeDate") - val challengeDate: String, -) diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointResponse.kt deleted file mode 100644 index 39af32f25..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/EarnPointResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class EarnPointResponse( - @SerialName("point") - val point: Int? = 0 -) \ No newline at end of file diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/PointListResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/PointListResponse.kt deleted file mode 100644 index 184c4ac1b..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/PointListResponse.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class PointListResponse( - @SerialName("point") - val point: Int? = 0, - @SerialName("period") - val period: Int? = 0, - @SerialName("challengePointStatuses") - val challengePointStatuses: List = emptyList(), -) { - @Serializable - data class ChallengePointStatus( - @SerialName("challengeDate") - val challengeDate: String? = "", - @SerialName("status") - val status: String? = "NONE" - ) -} \ No newline at end of file diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsablePointResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsablePointResponse.kt deleted file mode 100644 index 3d92bbdfd..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsablePointResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class UsablePointResponse( - @SerialName("point") - val point: Int? = 0 -) \ No newline at end of file diff --git a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsePointResponse.kt b/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsePointResponse.kt deleted file mode 100644 index 38b8ecebd..000000000 --- a/core/network/src/main/java/com/hmh/hamyeonham/core/network/point/model/UsePointResponse.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hmh.hamyeonham.core.network.point.model - - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class UsePointResponse( - @SerialName("usagePoint") - val usagePoint: Int? = 0, - @SerialName("userPoint") - val userPoint: Int? = 0 -) \ No newline at end of file diff --git a/core/viewmodel/main/build.gradle.kts b/core/viewmodel/main/build.gradle.kts index 2a6c58667..da4a54fb8 100644 --- a/core/viewmodel/main/build.gradle.kts +++ b/core/viewmodel/main/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { implementation(projects.domain.usagestats) implementation(projects.domain.challenge) implementation(projects.domain.userinfo) - implementation(projects.domain.point) implementation(projects.domain.lock) implementation(projects.domain.main) diff --git a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainEffect.kt b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainEffect.kt index a42443ec3..79f39ef0c 100644 --- a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainEffect.kt +++ b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainEffect.kt @@ -1,7 +1,5 @@ package com.hmh.hamyeonham.core.viewmodel sealed interface MainEffect { - data object SuccessUsePoint : MainEffect - data object LackOfPoint : MainEffect data object NetworkError : MainEffect } \ No newline at end of file diff --git a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt index 7b94ebf95..17011309f 100644 --- a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt +++ b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt @@ -11,7 +11,6 @@ import com.hmh.hamyeonham.core.domain.usagegoal.model.ChallengeStatus import com.hmh.hamyeonham.core.domain.usagegoal.model.UsageGoal import com.hmh.hamyeonham.core.domain.usagegoal.repository.UsageGoalsRepository import com.hmh.hamyeonham.domain.main.MainRepository -import com.hmh.hamyeonham.domain.point.repository.PointRepository import com.hmh.hamyeonham.lock.SetIsUnLockUseCase import com.hmh.hamyeonham.lock.UpdateIsUnLockUseCase import com.hmh.hamyeonham.usagestats.model.UsageStatusAndGoal @@ -41,7 +40,6 @@ class MainViewModel @Inject constructor( private val challengeRepository: ChallengeRepository, private val usageGoalsRepository: UsageGoalsRepository, private val userInfoRepository: UserInfoRepository, - private val pointRepository: PointRepository, private val mainRepository: MainRepository, private val getUsageStatsListUseCase: GetUsageStatsListUseCase, private val setIsUnLockUseCase: SetIsUnLockUseCase, @@ -69,13 +67,6 @@ class MainViewModel @Inject constructor( private val _challengeList = MutableStateFlow>(emptyList()) val challengeStatusList = _challengeList.asStateFlow() - private val _userPoint = MutableStateFlow(0) - val userPoint = _userPoint.asStateFlow() - - val isPointLeftToCollect - get() = - challengeStatusList.value.contains(ChallengeStatus.UNEARNED) - private val _effect = MutableSharedFlow() val effect = _effect.asSharedFlow() @@ -107,28 +98,12 @@ class MainViewModel @Inject constructor( } fun updateDailyChallengeFailed() { - viewModelScope.launch(Dispatchers.Main) { - pointRepository.usePoint().onSuccess { - _userPoint.value = it.userPoint - setIsUnLockUseCase(true).onSuccess { - getChallengeStatus() - sendEffect(MainEffect.SuccessUsePoint) - }.onFailure { e -> - Timber.e(e) - sendEffect(MainEffect.NetworkError) - } - }.onFailure { - if (it is HttpException) { - when (it.code()) { - LACK_POINT_ERROR_CODE -> { - sendEffect(MainEffect.LackOfPoint) - } - - else -> sendEffect(MainEffect.NetworkError) - } - } else { - sendEffect(MainEffect.NetworkError) - } + viewModelScope.launch { + setIsUnLockUseCase(true).onSuccess { + getChallengeStatus() + }.onFailure { e -> + Timber.e(e) + sendEffect(MainEffect.NetworkError) } } } @@ -157,10 +132,6 @@ class MainViewModel @Inject constructor( } } - fun updatePoint(point: Int) { - _userPoint.value = point - } - private fun updateState(transform: suspend MainState.() -> MainState) { viewModelScope.launch(Dispatchers.Main) { val currentState = mainState.value @@ -241,7 +212,6 @@ class MainViewModel @Inject constructor( updateState { copy(name = userInfo.name) } - _userPoint.value = userInfo.point } private fun setUsageStatsList(usageStatsList: UsageStatusAndGoal) { diff --git a/data/point/.gitignore b/data/point/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/data/point/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/data/point/build.gradle.kts b/data/point/build.gradle.kts deleted file mode 100644 index e49f0978a..000000000 --- a/data/point/build.gradle.kts +++ /dev/null @@ -1,15 +0,0 @@ -@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed -plugins { - hmh("feature") -} - -android { - namespace = "com.hmh.hamyeonham.data.point" -} - -dependencies { - implementation(projects.core.network) - implementation(projects.core.common) - - implementation(projects.domain.point) -} \ No newline at end of file diff --git a/data/point/src/main/AndroidManifest.xml b/data/point/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68a..000000000 --- a/data/point/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/data/point/src/main/java/com/hmh/hamyeonham/data/point/PointMapper.kt b/data/point/src/main/java/com/hmh/hamyeonham/data/point/PointMapper.kt deleted file mode 100644 index d489f565d..000000000 --- a/data/point/src/main/java/com/hmh/hamyeonham/data/point/PointMapper.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.hmh.hamyeonham.data.point - -import com.hmh.hamyeonham.core.network.point.model.EarnPointResponse -import com.hmh.hamyeonham.core.network.point.model.PointListResponse -import com.hmh.hamyeonham.core.network.point.model.UsablePointResponse -import com.hmh.hamyeonham.core.network.point.model.UsePointResponse -import com.hmh.hamyeonham.domain.point.model.EarnPoint -import com.hmh.hamyeonham.domain.point.model.PointInfo -import com.hmh.hamyeonham.domain.point.model.UsablePoint -import com.hmh.hamyeonham.domain.point.model.UsePoint - -fun EarnPointResponse.toEarnPoint() = EarnPoint( - totalUserPoint = point ?: 0 -) - -fun UsablePointResponse.toUsePoint() = UsablePoint( - point = point ?: 0 -) - -fun UsePointResponse.toUsePoint() = UsePoint( - usagePoint = usagePoint ?: 0, - userPoint = userPoint ?: 0 -) - -fun PointListResponse.toPointStatusList() = PointInfo( - period = period ?: 0, - currentUserPoint = point ?: 0, - challengePointStatuses = challengePointStatuses.map { - PointInfo.ChallengePointStatus( - challengeDate = it.challengeDate ?: "", - status = when (it.status) { - "UNEARNED" -> PointInfo.GetPointStatus.UNEARNED - "EARNED" -> PointInfo.GetPointStatus.EARNED - "FAILURE" -> PointInfo.GetPointStatus.FAILURE - else -> PointInfo.GetPointStatus.NONE - }, - period = this.period ?: 0, - challengePoint = 20 - ) - } -) \ No newline at end of file diff --git a/data/point/src/main/java/com/hmh/hamyeonham/data/point/di/PointBinder.kt b/data/point/src/main/java/com/hmh/hamyeonham/data/point/di/PointBinder.kt deleted file mode 100644 index e5c5ebad8..000000000 --- a/data/point/src/main/java/com/hmh/hamyeonham/data/point/di/PointBinder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.hmh.hamyeonham.data.point.di - -import com.hmh.hamyeonham.data.point.repository.DefaultPointRepository -import com.hmh.hamyeonham.domain.point.repository.PointRepository -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -interface PointBinder { - @Binds - @Singleton - fun bindPointRepository(repository: DefaultPointRepository): PointRepository -} \ No newline at end of file diff --git a/data/point/src/main/java/com/hmh/hamyeonham/data/point/repository/DefaultPointRepository.kt b/data/point/src/main/java/com/hmh/hamyeonham/data/point/repository/DefaultPointRepository.kt deleted file mode 100644 index 8e5e3b936..000000000 --- a/data/point/src/main/java/com/hmh/hamyeonham/data/point/repository/DefaultPointRepository.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.hmh.hamyeonham.data.point.repository - -import com.hmh.hamyeonham.common.time.getNowDateNumeric -import com.hmh.hamyeonham.core.network.point.PointService -import com.hmh.hamyeonham.core.network.point.model.ChallengeDateRequest -import com.hmh.hamyeonham.core.network.point.model.EarnPointRequest -import com.hmh.hamyeonham.data.point.toEarnPoint -import com.hmh.hamyeonham.data.point.toPointStatusList -import com.hmh.hamyeonham.data.point.toUsePoint -import com.hmh.hamyeonham.domain.point.model.EarnPoint -import com.hmh.hamyeonham.domain.point.model.PointInfo -import com.hmh.hamyeonham.domain.point.model.UsablePoint -import com.hmh.hamyeonham.domain.point.model.UsePoint -import com.hmh.hamyeonham.domain.point.repository.PointRepository -import javax.inject.Inject - -class DefaultPointRepository @Inject constructor( - private val pointService: PointService -) : PointRepository { - override suspend fun earnPoint(challengeDate: String): Result = runCatching { - pointService.earnPoint(EarnPointRequest(challengeDate)).data.toEarnPoint() - } - - override suspend fun getUsablePoint(): Result = runCatching { - pointService.getUsablePoint().toUsePoint() - } - - override suspend fun usePoint(): Result { - return runCatching { - val challengeDate = getNowDateNumeric() - val body = ChallengeDateRequest(challengeDate) - pointService.patchPoint(body).toUsePoint() - } - } - - override suspend fun getPointInfoList(): Result { - return runCatching { - pointService.getPointInfoList().data.toPointStatusList() - } - } -} \ No newline at end of file diff --git a/domain/point/.gitignore b/domain/point/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/domain/point/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/domain/point/build.gradle.kts b/domain/point/build.gradle.kts deleted file mode 100644 index 6afce4475..000000000 --- a/domain/point/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed -plugins { - `java-library` - kotlin("jvm") -} - -dependencies { - implementation(libs.javax.inject) -} \ No newline at end of file diff --git a/domain/point/src/main/AndroidManifest.xml b/domain/point/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68a..000000000 --- a/domain/point/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/EarnPoint.kt b/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/EarnPoint.kt deleted file mode 100644 index 6a9ce4a9a..000000000 --- a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/EarnPoint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.hmh.hamyeonham.domain.point.model - - -data class EarnPoint( - val totalUserPoint: Int -) \ No newline at end of file diff --git a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/PointInfo.kt b/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/PointInfo.kt deleted file mode 100644 index 70ec57221..000000000 --- a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/PointInfo.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.hmh.hamyeonham.domain.point.model - -data class PointInfo( - val period: Int = 0, - val currentUserPoint: Int = 0, - val challengePointStatuses: List = emptyList(), - val challengePoint: Int = 20, -) { - data class ChallengePointStatus( - val challengeDate: String = "", - val status: GetPointStatus = GetPointStatus.UNEARNED, - val period: Int, - val challengePoint: Int, - ) - - enum class GetPointStatus { - UNEARNED, - EARNED, - FAILURE, - NONE - } -} diff --git a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsablePoint.kt b/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsablePoint.kt deleted file mode 100644 index dbc6e1392..000000000 --- a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsablePoint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.hmh.hamyeonham.domain.point.model - - -data class UsablePoint( - val point: Int -) \ No newline at end of file diff --git a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsePoint.kt b/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsePoint.kt deleted file mode 100644 index 79766d23c..000000000 --- a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/model/UsePoint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.hmh.hamyeonham.domain.point.model - -data class UsePoint( - val usagePoint: Int, - val userPoint: Int -) diff --git a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/repository/PointRepository.kt b/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/repository/PointRepository.kt deleted file mode 100644 index e0a760cb3..000000000 --- a/domain/point/src/main/java/com/hmh/hamyeonham/domain/point/repository/PointRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hmh.hamyeonham.domain.point.repository - -import com.hmh.hamyeonham.domain.point.model.EarnPoint -import com.hmh.hamyeonham.domain.point.model.PointInfo -import com.hmh.hamyeonham.domain.point.model.UsablePoint -import com.hmh.hamyeonham.domain.point.model.UsePoint - -interface PointRepository { - suspend fun earnPoint(challengeDate: String): Result - suspend fun getUsablePoint(): Result - suspend fun usePoint(): Result - suspend fun getPointInfoList(): Result -} \ No newline at end of file diff --git a/feature/challenge/build.gradle.kts b/feature/challenge/build.gradle.kts index c25fef7ae..e17b94740 100644 --- a/feature/challenge/build.gradle.kts +++ b/feature/challenge/build.gradle.kts @@ -11,7 +11,6 @@ android { dependencies { implementation(projects.domain.usagestats) implementation(projects.domain.challenge) - implementation(projects.domain.point) implementation(projects.core.common) implementation(projects.core.designsystem) diff --git a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/ChallengeFragment.kt b/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/ChallengeFragment.kt index d44509636..bbd10f727 100644 --- a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/ChallengeFragment.kt +++ b/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/ChallengeFragment.kt @@ -28,7 +28,6 @@ import com.hmh.hamyeonham.challenge.newchallenge.NewChallengeActivity import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils import com.hmh.hamyeonham.common.context.getAppNameFromPackageName import com.hmh.hamyeonham.common.dialog.TwoButtonCommonDialog -import com.hmh.hamyeonham.common.fragment.snackBarWithAction import com.hmh.hamyeonham.common.fragment.toast import com.hmh.hamyeonham.common.fragment.viewLifeCycle import com.hmh.hamyeonham.common.fragment.viewLifeCycleScope @@ -78,18 +77,6 @@ class ChallengeFragment : Fragment() { @Inject lateinit var navigationProvider: NavigationProvider - private val pointResultLauncher: ActivityResultLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { - val point = result.data?.getIntExtra("point", 0) - if (point != null && point != 0) { - activityViewModel.updatePoint(point) - } - activityViewModel.reloadChallengeStatus() - } - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -119,9 +106,6 @@ class ChallengeFragment : Fragment() { activityViewModel.challengeStatusList.flowWithLifecycle(viewLifeCycle).onEach { bindChallengeCalendar(it) - val pointButtonImg = - if (activityViewModel.isPointLeftToCollect) com.hmh.hamyeonham.common.R.drawable.ic_chellenge_point_exist_24 else com.hmh.hamyeonham.common.R.drawable.ic_chellenge_point_not_exist_24 - binding.tvPointButton.setImageResource(pointButtonImg) }.launchIn(viewLifeCycleScope) } @@ -182,19 +166,6 @@ class ChallengeFragment : Fragment() { } } - private fun initPointButton() { - val pointButtonImg = - if (activityViewModel.isPointLeftToCollect) { - com.hmh.hamyeonham.common.R.drawable.ic_chellenge_point_exist_24 - } else { - com.hmh.hamyeonham.common.R.drawable.ic_chellenge_point_not_exist_24 - } - binding.tvPointButton.setImageResource(pointButtonImg) - binding.tvPointButton.setOnClickListener { - navigateToPointView() - } - } - private fun initAppAddButton() { binding.btGoalAdd.setOnSingleClickListener { AmplitudeUtils.trackEventWithProperties("click_add_button") @@ -206,31 +177,13 @@ class ChallengeFragment : Fragment() { private fun initChallengeCreateButton() { binding.btnChallengeCreate.setOnClickListener { AmplitudeUtils.trackEventWithProperties("click_newchallenge_button") - if (activityViewModel.isPointLeftToCollect) { - snackBarWithAction( - anchorView = binding.root, - message = getString(com.hmh.hamyeonham.feature.challenge.R.string.challenge_cannot_create), - actionMessage = getString( - com.hmh.hamyeonham.feature.challenge.R.string.all_move, - ), - ) { - navigateToPointView() - } - } else { - val intent = Intent(requireContext(), NewChallengeActivity::class.java) - newChallengeResultLauncher.launch(intent) - } + val intent = Intent(requireContext(), NewChallengeActivity::class.java) + newChallengeResultLauncher.launch(intent) } } - private fun navigateToPointView() { - val intent = navigationProvider.toPoint() - pointResultLauncher.launch(intent) - } - private fun initViews() { initModifierButton() - initPointButton() initAppAddButton() initChallengeCreateButton() initChallengeGoalsRecyclerView() diff --git a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointActivity.kt b/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointActivity.kt deleted file mode 100644 index d4a507a34..000000000 --- a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointActivity.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.hmh.hamyeonham.challenge.point - -import android.os.Bundle -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils -import com.hmh.hamyeonham.common.context.toast -import com.hmh.hamyeonham.common.view.viewBinding -import com.hmh.hamyeonham.feature.challenge.databinding.ActivityPointBinding -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -@AndroidEntryPoint -class PointActivity : AppCompatActivity() { - private val binding by viewBinding(ActivityPointBinding::inflate) - private val viewModel by viewModels() - - override fun onResume() { - super.onResume() - AmplitudeUtils.trackEventWithProperties("view_point") - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(binding.root) - initViews() - collectPointInfo() - collectUserPoint() - collectPointSuccessState() - } - - private fun initViews() { - initAdapter() - initCloseClickListener() - } - - private fun initAdapter() { - val adapter = PointAdapter( - onButtonClick = { challengeDate -> - viewModel.earnChallengePoint(challengeDate) - }, - ) - binding.rvPoint.run { - this.adapter = adapter - layoutManager = LinearLayoutManager(context) - } - } - - private fun initCloseClickListener() { - binding.ivBack.setOnClickListener { - finish() - } - } - - private fun collectPointInfo() { - viewModel.pointInfoList.flowWithLifecycle(lifecycle).onEach { pointInfoList -> - (binding.rvPoint.adapter as? PointAdapter)?.submitList(pointInfoList) - }.launchIn(lifecycleScope) - } - - private fun collectUserPoint() { - viewModel.currentPointState.flowWithLifecycle(lifecycle).onEach { - binding.tvPointTotal.text = it.toString() - setResult(RESULT_OK, intent.putExtra("point", it)) - }.launchIn(lifecycleScope) - } - - private fun collectPointSuccessState() { - viewModel.getPointSuccess.flowWithLifecycle(lifecycle).onEach { - if(it) { - toast("포인트를 획득했어요!") - } - }.launchIn(lifecycleScope) - } -} diff --git a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointAdapter.kt b/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointAdapter.kt deleted file mode 100644 index c52824d4b..000000000 --- a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointAdapter.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.hmh.hamyeonham.challenge.point - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.appcompat.content.res.AppCompatResources -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.hmh.hamyeonham.common.view.ItemDiffCallback -import com.hmh.hamyeonham.domain.point.model.PointInfo -import com.hmh.hamyeonham.feature.challenge.R -import com.hmh.hamyeonham.feature.challenge.databinding.ItemPointBinding - -class PointAdapter( - private val onButtonClick: (String) -> Unit, -) : ListAdapter( - ItemDiffCallback( - onItemsTheSame = { oldItem, newItem -> - oldItem == newItem - }, - onContentsTheSame = { oldItem, newItem -> - oldItem == newItem - }, - ), -) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PointViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = ItemPointBinding.inflate(inflater, parent, false) - return PointViewHolder( - binding, - onButtonClick, - ) - } - - override fun onBindViewHolder(holder: PointViewHolder, position: Int) { - holder.onBind(getItem(position)) - } - - class PointViewHolder( - private val binding: ItemPointBinding, - private val onButtonClick: (String) -> Unit, - ) : RecyclerView.ViewHolder(binding.root) { - private val context = binding.root.context - - fun onBind(pointModel: PointInfo.ChallengePointStatus) { - binding.run { - - tvPointTitle.text = - context.getString(R.string.tv_point_title, (adapterPosition + 1).toString()) - tvPointWhatChallenge.text = - context.getString( - R.string.tv_point_what_challenge, - pointModel.period.toString() - ) - tvPointButton.text = - context.getString(R.string.point_point, pointModel.challengePoint.toString()) - - setPointButtonStatus(pointModel) - } - } - - private fun ItemPointBinding.setPointButtonStatus(pointModel: PointInfo.ChallengePointStatus) { - - val textColorNone = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.gray3 - ) - val textColorEarned = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.blue_purple_opacity_70 - ) - - val textColorTitle = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.white_text - ) - - val textColorUnearned = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.white_btn - ) - - val textColorSubscription = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.gray2 - ) - - - - val textColorFailure = ContextCompat.getColor( - context, - com.hmh.hamyeonham.core.designsystem.R.color.gray2 - ) - - val buttonBackgroundEarned = AppCompatResources.getDrawable( - context, - R.drawable.point_button_background_already_get - ) - - val buttonBackgroundUnearned = AppCompatResources.getDrawable( - context, - R.drawable.point_button_background_able - ) - - val buttonBackgroundFailure = AppCompatResources.getDrawable( - context, - R.drawable.point_button_background_failure - ) - - val buttonBackgroundNone = AppCompatResources.getDrawable( - context, - R.drawable.point_button_background_none - ) - - when (pointModel.status) { - // 이미 받은 경우 - PointInfo.GetPointStatus.EARNED -> { - setTextColor(textColorTitle, textColorSubscription) - tvPointButton.isEnabled = false - tvPointButton.background = buttonBackgroundEarned - tvPointButton.setTextColor(textColorEarned) - } - // 받을 수 있는 경우 - PointInfo.GetPointStatus.UNEARNED -> { - tvPointTitle.setTextColor(textColorTitle) - tvPointWhatChallenge.setTextColor(textColorSubscription) - tvPointButton.isEnabled = true - tvPointButton.background = buttonBackgroundUnearned - tvPointButton.setTextColor(textColorUnearned) - tvPointButton.setOnClickListener { - onButtonClick(pointModel.challengeDate) - } - } - // 챌린지 실패로 인한 포인트 획득 불가 - PointInfo.GetPointStatus.FAILURE -> { - tvPointTitle.setTextColor(textColorTitle) - tvPointWhatChallenge.setTextColor(textColorSubscription) - tvPointButton.isEnabled = false - tvPointButton.background = buttonBackgroundFailure - tvPointButton.setTextColor(textColorFailure) - } - // 아직 챌린지를 시도하지 않은 날짜 - PointInfo.GetPointStatus.NONE -> { - tvPointTitle.setTextColor(textColorNone) - tvPointWhatChallenge.setTextColor(textColorNone) - tvPointButton.isEnabled = false - tvPointButton.background = buttonBackgroundNone - tvPointButton.setTextColor(textColorNone) - } - } - } - - private fun ItemPointBinding.setTextColor( - textColorTitle: Int, - textColorSubscription: Int - ) { - tvPointTitle.setTextColor(textColorTitle) - tvPointWhatChallenge.setTextColor(textColorSubscription) - } - } -} - diff --git a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointViewModel.kt b/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointViewModel.kt deleted file mode 100644 index b791de770..000000000 --- a/feature/challenge/src/main/java/com/hmh/hamyeonham/challenge/point/PointViewModel.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.hmh.hamyeonham.challenge.point - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.hmh.hamyeonham.common.amplitude.AmplitudeUtils -import com.hmh.hamyeonham.domain.point.model.PointInfo -import com.hmh.hamyeonham.domain.point.repository.PointRepository -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import org.json.JSONObject -import javax.inject.Inject - -@HiltViewModel -class PointViewModel @Inject constructor( - private val pointRepository: PointRepository, -) : ViewModel() { - private val _currentUserPoint = MutableStateFlow(0) - val currentPointState = _currentUserPoint.asStateFlow() - - private val _pointInfoList = MutableStateFlow(emptyList()) - val pointInfoList = _pointInfoList.asStateFlow() - - private val _getPointSuccess = MutableStateFlow(false) - val getPointSuccess = _getPointSuccess.asStateFlow() - - - init { - getPointInfoList() - } - - fun earnChallengePoint(challengeDate: String) { - viewModelScope.launch { - pointRepository.earnPoint(challengeDate).onSuccess { - val properties = JSONObject().put("get_point_date", challengeDate) - AmplitudeUtils.trackEventWithProperties("click_getpoint_button", properties) - getPointInfoList() - _getPointSuccess.value = true - } - } - } - - private fun getPointInfoList() { - viewModelScope.launch { - pointRepository.getPointInfoList().onSuccess { - _pointInfoList.value = it.challengePointStatuses - _currentUserPoint.value = it.currentUserPoint - } - } - } -} diff --git a/feature/challenge/src/main/res/layout/activity_point.xml b/feature/challenge/src/main/res/layout/activity_point.xml deleted file mode 100644 index ae1bc7858..000000000 --- a/feature/challenge/src/main/res/layout/activity_point.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/feature/challenge/src/main/res/layout/fragment_challenge.xml b/feature/challenge/src/main/res/layout/fragment_challenge.xml index bddae7be9..fd6dd4d36 100644 --- a/feature/challenge/src/main/res/layout/fragment_challenge.xml +++ b/feature/challenge/src/main/res/layout/fragment_challenge.xml @@ -30,15 +30,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - when (effect) { - is MainEffect.SuccessUsePoint -> { - intent.removeExtra(NavigationProvider.UN_LOCK_PACKAGE_NAME) - showChallengeFailedDialog() - } - - is MainEffect.LackOfPoint -> showPointLackDialog() is MainEffect.NetworkError -> showErrorDialog() } }.launchIn(lifecycleScope) @@ -101,10 +94,10 @@ class MainActivity : AppCompatActivity() { val packageName = intent.getStringExtra(NavigationProvider.UN_LOCK_PACKAGE_NAME) ?: return TwoButtonCommonDialog.newInstance( title = - getString( - R.string.dialog_title_unlock_package, - getAppNameFromPackageName(packageName), - ), + getString( + R.string.dialog_title_unlock_package, + getAppNameFromPackageName(packageName), + ), description = getString(R.string.dialog_description_unlock_package), confirmButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_okay), dismissButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_cancel), @@ -124,38 +117,6 @@ class MainActivity : AppCompatActivity() { } } - private fun showChallengeFailedDialog() { - OneButtonCommonDialog.newInstance( - title = getString(R.string.dialog_title_challenge_failed), - description = getString(R.string.dialog_description_challenge_failed), - iconRes = R.drawable.ic_challenge_failed, - confirmButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_done), - ).apply { - setConfirmButtonClickListener { - AmplitudeUtils.trackEventWithProperties("click_unlock_complete_button") - dismiss() - } - }.showAllowingStateLoss(supportFragmentManager, OneButtonCommonDialog.TAG) - } - - private fun showPointLackDialog() { - TwoButtonCommonDialog.newInstance( - title = getString(R.string.dialog_title_point_lack), - description = getString(R.string.dialog_description_point_lack), - iconRes = R.drawable.ic_point_lack, - confirmButtonText = getString(com.hmh.hamyeonham.core.designsystem.R.string.all_okay), - dismissButtonText = getString(R.string.dialog_button_charge_point) - ).apply { - setConfirmButtonClickListener { - dismiss() - } - setDismissButtonClickListener { - val intent = navigationProvider.toStore() - startActivity(intent) - } - }.showAllowingStateLoss(supportFragmentManager, OneButtonCommonDialog.TAG) - } - private fun showErrorDialog() { OneButtonCommonDialog.newInstance( title = getString(com.hmh.hamyeonham.core.designsystem.R.string.dialog_title_network_error), diff --git a/feature/main/src/main/res/values/strings.xml b/feature/main/src/main/res/values/strings.xml index 2b55a25f7..59acf73f4 100644 --- a/feature/main/src/main/res/values/strings.xml +++ b/feature/main/src/main/res/values/strings.xml @@ -22,12 +22,6 @@ 사용 시간을 연장하면\n챌린지를 실패해요 블랙홀에 빠졌어요\n내일은 목표를 이뤄보아요 - 100P로 잠금을 해제해\n미션에 실패했어요 - 다음엔 꼭 성공해보아요 - - 포인트가 부족해요 - 상점에서 포인트를 충전할 수 있어요 - 포인트 구매하기 권한 허용하기 스크린타임 권한을 허용하면 여정이 시작돼요. diff --git a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt index ac48dd461..3f71dad2d 100644 --- a/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt +++ b/feature/mypage/src/main/java/com/hmh/hamyeonham/mypage/MyPageFragment.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.json.JSONObject import javax.inject.Inject +import androidx.core.net.toUri @AndroidEntryPoint class MyPageFragment : Fragment() { @@ -117,16 +118,12 @@ class MyPageFragment : Fragment() { activityViewModel.mainState.flowWithLifecycle(viewLifeCycle).onEach { binding.tvUserName.text = it.name }.launchIn(viewLifeCycleScope) - - activityViewModel.userPoint.flowWithLifecycle(viewLifeCycle).onEach { - binding.tvPoint.text = getString(R.string.mypage_point, it) - }.launchIn(viewLifeCycleScope) } private fun initPrivacyButton() { binding.vPrivacy.setOnClickListener { val privacyRuleUrl = getString(R.string.privacy_url) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(privacyRuleUrl)) + val intent = Intent(Intent.ACTION_VIEW, privacyRuleUrl.toUri()) startActivity(intent) } } @@ -134,7 +131,7 @@ class MyPageFragment : Fragment() { private fun initTermOfUseButton() { binding.vTermofuse.setOnClickListener { val termOfUseUrl = getString(R.string.term_of_use_url) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(termOfUseUrl)) + val intent = Intent(Intent.ACTION_VIEW, termOfUseUrl.toUri()) startActivity(intent) } } diff --git a/feature/mypage/src/main/res/layout/fragment_my_page.xml b/feature/mypage/src/main/res/layout/fragment_my_page.xml index 7afa2c63b..5e0395f20 100644 --- a/feature/mypage/src/main/res/layout/fragment_my_page.xml +++ b/feature/mypage/src/main/res/layout/fragment_my_page.xml @@ -1,7 +1,6 @@ @@ -63,51 +62,6 @@ app:layout_constraintStart_toStartOf="@id/iv_profile" app:layout_constraintTop_toBottomOf="@id/iv_profile" /> - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/tv_user_name" /> + app:layout_constraintTop_toBottomOf="@id/tv_user_name" /> Date: Thu, 24 Jul 2025 23:54:57 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Point=20=EC=A0=9C=EA=B1=B0=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EB=A7=88=EB=AC=B4=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/hmh/hamyeonham/common/context/ContextExt.kt | 2 -- .../java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt | 5 ----- .../java/com/hmh/hamyeonham/userinfo/mapper/MypageMapper.kt | 3 +-- .../userinfo/repository/DefaultUserInfoRepository.kt | 2 +- .../main/java/com/hmh/hamyeonham/userinfo/model/UserInfo.kt | 1 - feature/mypage/src/main/res/values/strings.xml | 2 -- settings.gradle.kts | 2 -- 7 files changed, 2 insertions(+), 15 deletions(-) diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt index 21d062647..60e17dec0 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt @@ -1,10 +1,8 @@ package com.hmh.hamyeonham.common.context import android.Manifest -import android.app.Activity import android.app.Dialog import android.content.Context -import android.content.ContextWrapper import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Point diff --git a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt index 17011309f..a467dc51d 100644 --- a/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt +++ b/core/viewmodel/main/src/main/java/com/hmh/hamyeonham/core/viewmodel/MainViewModel.kt @@ -258,9 +258,4 @@ class MainViewModel @Inject constructor( return items } - - - companion object { - private const val LACK_POINT_ERROR_CODE = 400 - } } diff --git a/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/mapper/MypageMapper.kt b/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/mapper/MypageMapper.kt index 474805cae..ae9289c18 100644 --- a/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/mapper/MypageMapper.kt +++ b/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/mapper/MypageMapper.kt @@ -1,4 +1,4 @@ -package com.hmh.hamyeonham.login.mapper +package com.hmh.hamyeonham.userinfo.mapper import com.hmh.hamyeonham.core.network.mypage.datasource.model.UserInfoResponse import com.hmh.hamyeonham.userinfo.model.UserInfo @@ -6,6 +6,5 @@ import com.hmh.hamyeonham.userinfo.model.UserInfo internal fun UserInfoResponse.toUserInfo(): UserInfo { return UserInfo( name = name, - point = point ) } diff --git a/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/repository/DefaultUserInfoRepository.kt b/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/repository/DefaultUserInfoRepository.kt index 4db0705a2..2b2ee121c 100644 --- a/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/repository/DefaultUserInfoRepository.kt +++ b/data/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/repository/DefaultUserInfoRepository.kt @@ -1,7 +1,7 @@ package com.hmh.hamyeonham.userinfo.repository import com.hmh.hamyeonham.core.network.mypage.MyPageService -import com.hmh.hamyeonham.login.mapper.toUserInfo +import com.hmh.hamyeonham.userinfo.mapper.toUserInfo import com.hmh.hamyeonham.userinfo.model.UserInfo import javax.inject.Inject diff --git a/domain/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/model/UserInfo.kt b/domain/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/model/UserInfo.kt index 9337ee4ff..73aa09944 100644 --- a/domain/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/model/UserInfo.kt +++ b/domain/userinfo/src/main/java/com/hmh/hamyeonham/userinfo/model/UserInfo.kt @@ -2,5 +2,4 @@ package com.hmh.hamyeonham.userinfo.model data class UserInfo( val name: String = "", - val point: Int = 0 ) diff --git a/feature/mypage/src/main/res/values/strings.xml b/feature/mypage/src/main/res/values/strings.xml index 70141f274..f7ddee2e6 100644 --- a/feature/mypage/src/main/res/values/strings.xml +++ b/feature/mypage/src/main/res/values/strings.xml @@ -1,7 +1,5 @@ 마이페이지 - 내 포인트 - %dP 배지 컬렉션 지금까지의 여정 우주 상점 diff --git a/settings.gradle.kts b/settings.gradle.kts index 98027ab38..73d2ff297 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,7 +34,6 @@ include(":data:userinfo") include(":data:login") include(":data:device") include(":data:challenge") -include(":data:point") include(":data:lock") include(":data:main") @@ -43,7 +42,6 @@ include(":domain:usagestats") include(":domain:challenge") include(":domain:login") include(":domain:onboarding") -include(":domain:point") include(":domain:lock") include(":domain:main") From c05add5d2faeae7e8f298d969524870c3ff62a5b Mon Sep 17 00:00:00 2001 From: Kez Date: Sun, 3 Aug 2025 11:21:34 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Lint=20Error=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/challenge/src/main/AndroidManifest.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/feature/challenge/src/main/AndroidManifest.xml b/feature/challenge/src/main/AndroidManifest.xml index de9879787..a2b21cd56 100644 --- a/feature/challenge/src/main/AndroidManifest.xml +++ b/feature/challenge/src/main/AndroidManifest.xml @@ -5,9 +5,6 @@ - Date: Sun, 3 Aug 2025 15:08:41 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Kakao=20Login=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/develop_PR_builder.yml | 1 - .github/workflows/release.yml | 1 - app/build.gradle.kts | 4 - app/proguard-rules.pro | 5 - app/src/main/AndroidManifest.xml | 15 -- .../java/com/hmh/hamyeonham/HMHApplication.kt | 2 - .../java/com/hmh/hamyeonham/StartActivity.kt | 4 +- .../navigation/DefaultNavigationProvider.kt | 5 - .../hmh/hamyeonham/plugin/CommonConfigs.kt | 4 - .../common/navigation/NavigationProvider.kt | 1 - .../common/permission/PermissionActivity.kt | 22 +- .../common/permission/PermissionExt.kt | 32 ++- .../hmh/hamyeonham/common/qualifier/OAuth.kt | 7 - .../core/database/HMHRoomDatabase.kt | 14 +- .../core/database/dao/UserAuthDao.kt | 26 --- .../core/database/dao/UserProfileDao.kt | 23 -- .../hamyeonham/core/database/di/DaoModule.kt | 12 -- .../core/database/entity/UserAuth.kt | 18 -- .../core/database/entity/UserProfile.kt | 19 -- .../src/main/res/values/themes.xml | 5 +- core/network/build.gradle.kts | 2 - .../auth/authenticator/AuthenticatorUtil.kt | 38 ---- .../auth/authenticator/HMHAuthenticator.kt | 2 - .../auth/interceptor/AuthInterceptor.kt | 15 +- .../hamyeonham/core/network/di/NetModule.kt | 12 -- data/login/.gitignore | 1 - data/login/build.gradle.kts | 18 -- data/login/src/main/AndroidManifest.xml | 4 - .../datasource/kakao/KakaoAuthCodeClient.kt | 201 ------------------ .../kakao/KakaoSocialAuthDataStoreImpl.kt | 110 ---------- .../com/hmh/hamyeonham/login/di/AuthBinder.kt | 29 --- .../hamyeonham/login/di/AuthDataSourceKey.kt | 7 - .../login/mapper/UserProfileMapper.kt | 47 ---- .../login/repository/LocalAuthRepository.kt | 109 ---------- domain/login/.gitignore | 1 - domain/login/build.gradle.kts | 10 - domain/login/src/main/AndroidManifest.xml | 4 - .../hmh/hamyeonham/login/di/AuthProvider.kt | 7 - .../com/hmh/hamyeonham/login/model/Login.kt | 7 - .../hmh/hamyeonham/login/model/LoginInfo.kt | 13 -- .../login/model/SignRequestDomain.kt | 22 -- .../hmh/hamyeonham/login/model/SignUpUser.kt | 7 - .../com/hmh/hamyeonham/login/model/User.kt | 18 -- .../hmh/hamyeonham/login/model/UserInfo.kt | 21 -- .../hmh/hamyeonham/login/model/UserProfile.kt | 13 -- .../login/repository/AuthRepository.kt | 51 ----- .../login/repository/SocialAuthDataStore.kt | 33 --- .../hamyeonham/login/usecase/AuthUseCase.kt | 73 ------- feature/login/build.gradle.kts | 6 - feature/login/src/main/AndroidManifest.xml | 5 - .../hamyeonham/feature/login/LoginActivity.kt | 104 --------- .../feature/login/LoginViewModel.kt | 70 ------ .../feature/login/LoginViewPagerAdapter.kt | 49 ----- .../src/main/res/layout/activity_login.xml | 15 -- feature/login/src/main/res/values/strings.xml | 3 - feature/mypage/build.gradle.kts | 1 - .../hmh/hamyeonham/mypage/MyPageFragment.kt | 8 +- .../mypage/viewmodel/MyPageViewModel.kt | 24 +-- feature/onboarding/build.gradle.kts | 1 - .../viewmodel/OnBoardingViewModel.kt | 29 --- gradle/libs.versions.toml | 4 - settings.gradle.kts | 3 - 62 files changed, 43 insertions(+), 1374 deletions(-) delete mode 100644 core/common/src/main/java/com/hmh/hamyeonham/common/qualifier/OAuth.kt delete mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt delete mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt delete mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt delete mode 100644 core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt delete mode 100644 core/network/src/main/java/com/hmh/hamyeonham/core/network/auth/authenticator/AuthenticatorUtil.kt delete mode 100644 data/login/.gitignore delete mode 100644 data/login/build.gradle.kts delete mode 100644 data/login/src/main/AndroidManifest.xml delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoAuthCodeClient.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/datasource/kakao/KakaoSocialAuthDataStoreImpl.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthBinder.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/di/AuthDataSourceKey.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/mapper/UserProfileMapper.kt delete mode 100644 data/login/src/main/java/com/hmh/hamyeonham/login/repository/LocalAuthRepository.kt delete mode 100644 domain/login/.gitignore delete mode 100644 domain/login/build.gradle.kts delete mode 100644 domain/login/src/main/AndroidManifest.xml delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/di/AuthProvider.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/Login.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/LoginInfo.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignRequestDomain.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/SignUpUser.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/User.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserInfo.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/model/UserProfile.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/repository/AuthRepository.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/repository/SocialAuthDataStore.kt delete mode 100644 domain/login/src/main/java/com/hmh/hamyeonham/login/usecase/AuthUseCase.kt delete mode 100644 feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt delete mode 100644 feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt delete mode 100644 feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewPagerAdapter.kt diff --git a/.github/workflows/develop_PR_builder.yml b/.github/workflows/develop_PR_builder.yml index f458ba660..99aceb160 100644 --- a/.github/workflows/develop_PR_builder.yml +++ b/.github/workflows/develop_PR_builder.yml @@ -38,7 +38,6 @@ jobs: - name: Add Local Properties env: - KAKAO_API_KEY: ${{ secrets.KAKAO_API_KEY }} HMH_DEV_BASE_URL: ${{ secrets.HMH_DEV_BASE_URL }} HMH_PROD_BASE_URL: ${{ secrets.HMH_PROD_BASE_URL }} AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c49cfb838..9feac4e75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,6 @@ jobs: - name: Add Local Properties env: - KAKAO_API_KEY: ${{ secrets.KAKAO_API_KEY }} HMH_DEV_BASE_URL: ${{ secrets.HMH_DEV_BASE_URL }} HMH_PROD_BASE_URL: ${{ secrets.HMH_PROD_BASE_URL }} AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 08f844047..20ba44059 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,7 +73,6 @@ dependencies { // Domain implementation(projects.domain.usagestats) implementation(projects.domain.userinfo) - implementation(projects.domain.login) implementation(projects.domain.challenge) implementation(projects.domain.onboarding) implementation(projects.domain.lock) @@ -81,7 +80,6 @@ dependencies { // Data implementation(projects.data.usagestats) implementation(projects.data.userinfo) - implementation(projects.data.login) implementation(projects.data.challenge) implementation(projects.data.device) implementation(projects.data.onboarding) @@ -105,8 +103,6 @@ dependencies { implementation(libs.splash.screen) implementation(libs.lottie) - // kakao - implementation(libs.kakao.login) // Hilt Worker implementation(libs.androidx.hilt.common) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 9ce8d9180..13ed258aa 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -5,11 +5,6 @@ # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -##---------------Begin: Kakao SDK ---------- --keep class com.kakao.sdk.**.model.* { ; } --keep class * extends com.google.gson.TypeAdapter -##---------------END: Kakao SDK ---------- - ##---------------Begin: Okio ---------- # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97e7c1b35..a96454722 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,21 +46,6 @@ - - - - - - - - - - - diff --git a/app/src/main/java/com/hmh/hamyeonham/HMHApplication.kt b/app/src/main/java/com/hmh/hamyeonham/HMHApplication.kt index 6f58f2185..7b3f8d600 100644 --- a/app/src/main/java/com/hmh/hamyeonham/HMHApplication.kt +++ b/app/src/main/java/com/hmh/hamyeonham/HMHApplication.kt @@ -7,7 +7,6 @@ import com.amplitude.api.Amplitude import com.hmh.hamyeonham.core.notification.AppNotificationManager import com.hmh.hamyeonham.firebase.setFirebaseCrashlyticsEnabled import com.hmh.hamyeonham.hus.usagestats.HMHUsageStatsManager -import com.kakao.sdk.common.KakaoSdk import dagger.hilt.EntryPoint import dagger.hilt.EntryPoints import dagger.hilt.InstallIn @@ -35,7 +34,6 @@ class HMHApplication : Application(), Configuration.Provider { super.onCreate() setFirebaseCrashlyticsEnabled(!BuildConfig.DEBUG) setAmplitude() - KakaoSdk.init(this, BuildConfig.KAKAO_API_KEY) HMHUsageStatsManager.init(this) notificationManager.setupNotificationChannel() } diff --git a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt index 781f24b9a..641df6a12 100644 --- a/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt +++ b/app/src/main/java/com/hmh/hamyeonham/StartActivity.kt @@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.hmh.hamyeonham.common.view.viewBinding import com.hmh.hamyeonham.databinding.ActivitySampleBinding -import com.hmh.hamyeonham.feature.login.LoginActivity +import com.hmh.hamyeonham.feature.main.MainActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -31,7 +31,7 @@ class StartActivity : AppCompatActivity() { } private fun navigateToLogin() { - startActivity(Intent(this, LoginActivity::class.java)) + startActivity(Intent(this, MainActivity::class.java)) finish() } } diff --git a/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt b/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt index 84aa868a3..6d1090c8a 100644 --- a/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt +++ b/app/src/main/java/com/hmh/hamyeonham/navigation/DefaultNavigationProvider.kt @@ -5,7 +5,6 @@ import android.content.Intent import com.hmh.hamyeonham.common.navigation.NavigationProvider import com.hmh.hamyeonham.common.permission.PermissionActivity import com.hmh.hamyeonham.feature.lock.LockActivity -import com.hmh.hamyeonham.feature.login.LoginActivity import com.hmh.hamyeonham.feature.main.MainActivity import com.hmh.hamyeonham.feature.onboarding.OnBoardingActivity import com.hmh.hamyeonham.feature.onboarding.OnBoardingStoryActivity @@ -24,10 +23,6 @@ class DefaultNavigationProvider @Inject constructor( return Intent(context, OnBoardingStoryActivity::class.java) } - override fun toLogin(): Intent { - return Intent(context, LoginActivity::class.java) - } - override fun toMain(): Intent { return Intent(context, MainActivity::class.java) } diff --git a/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt b/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt index 043182a98..73a8c7aaf 100644 --- a/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt +++ b/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt @@ -22,10 +22,6 @@ internal fun Project.configureAndroidCommonPlugin() { extensions.getByType().apply { defaultConfig { - val kakaoApiKey = properties["kakaoApiKey"] as? String ?: "" - manifestPlaceholders["kakaoApiKey"] = properties["kakaoApiKey"] as String - buildConfigField("String", "KAKAO_API_KEY", "\"${kakaoApiKey}\"") - val amplitudeApiKey = properties["amplitudeApiKey"] as? String ?: "" buildConfigField("String", "AMPLITUDE_API_KEY", "\"${amplitudeApiKey}\"") } diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt index 3b0180bd1..f1282a47f 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/navigation/NavigationProvider.kt @@ -10,7 +10,6 @@ interface NavigationProvider { fun toOnBoarding(): Intent fun toOnBoardingStory(): Intent - fun toLogin(): Intent fun toMain(): Intent fun toLock(packageName: String): Intent fun toStore(): Intent diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionActivity.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionActivity.kt index 1dd6105e2..ee5e95254 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionActivity.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionActivity.kt @@ -1,14 +1,16 @@ package com.hmh.hamyeonham.common.permission -import android.app.usage.UsageStatsManager +import android.app.AppOpsManager import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.provider.Settings import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import com.hmh.hamyeonham.common.context.hasNotificationPermission import com.hmh.hamyeonham.common.context.toast import com.hmh.hamyeonham.common.databinding.ActivityPermissionBinding @@ -115,27 +117,11 @@ class PermissionActivity : AppCompatActivity() { private fun requestUsageAccessPermission() { try { - val packageUri = Uri.parse("package:$packageName") + val packageUri = "package:$packageName".toUri() val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS, packageUri) usageStatsPermissionLauncher.launch(intent) } catch (e: Exception) { usageStatsPermissionLauncher.launch(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) } } - - private fun hasUsageStatsPermission(): Boolean { - val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as? UsageStatsManager - val time = System.currentTimeMillis() - val stats = usageStatsManager?.queryUsageStats( - UsageStatsManager.INTERVAL_DAILY, - time - 1000 * 60, - time, - ) - return !stats.isNullOrEmpty() - - } - - private fun hasOverlayPermission(): Boolean { - return Settings.canDrawOverlays(this) - } } \ No newline at end of file diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionExt.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionExt.kt index 6d6c3f9e9..10983cf50 100644 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionExt.kt +++ b/core/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionExt.kt @@ -1,6 +1,7 @@ package com.hmh.hamyeonham.common.permission import android.Manifest +import android.app.AppOpsManager import android.app.usage.UsageStatsManager import android.content.Context import android.content.Intent @@ -38,14 +39,29 @@ fun AppCompatActivity.requestUsageAccessPermission() { } fun AppCompatActivity.hasUsageStatsPermission(): Boolean { - val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager - val time = System.currentTimeMillis() - val stats = usageStatsManager.queryUsageStats( - UsageStatsManager.INTERVAL_DAILY, - time - 1000 * 60, - time, - ) - return stats != null && stats.isNotEmpty() + val ops = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ops.unsafeCheckOpNoThrow( + AppOpsManager.OPSTR_GET_USAGE_STATS, + android.os.Process.myUid(), + packageName + ) + } else { + ops.checkOpNoThrow( + AppOpsManager.OPSTR_GET_USAGE_STATS, + android.os.Process.myUid(), + packageName + ) + } + + return when (mode) { + AppOpsManager.MODE_ALLOWED -> true + AppOpsManager.MODE_DEFAULT, + AppOpsManager.MODE_IGNORED, + AppOpsManager.MODE_ERRORED -> false + + else -> false + } } fun AppCompatActivity.hasOverlayPermission(): Boolean { diff --git a/core/common/src/main/java/com/hmh/hamyeonham/common/qualifier/OAuth.kt b/core/common/src/main/java/com/hmh/hamyeonham/common/qualifier/OAuth.kt deleted file mode 100644 index 7d85fe04c..000000000 --- a/core/common/src/main/java/com/hmh/hamyeonham/common/qualifier/OAuth.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.hmh.hamyeonham.common.qualifier - -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Kakao diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt index dd294e3c5..bf37865b0 100644 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/HMHRoomDatabase.kt @@ -9,10 +9,6 @@ import com.hmh.hamyeonham.core.database.dao.DeletedGoalsDao import com.hmh.hamyeonham.core.database.dao.LockDao import com.hmh.hamyeonham.core.database.dao.UsageGoalsDao import com.hmh.hamyeonham.core.database.dao.UsageTotalGoalDao -import com.hmh.hamyeonham.core.database.dao.UserAuthDao -import com.hmh.hamyeonham.core.database.dao.UserProfileDao -import com.hmh.hamyeonham.core.database.entity.UserAuth -import com.hmh.hamyeonham.core.database.entity.UserProfile import com.hmh.hamyeonham.core.database.model.DailyChallengeEntity import com.hmh.hamyeonham.core.database.model.DeletedGoalWithUsageEntity import com.hmh.hamyeonham.core.database.model.DeletedUsageEntity @@ -33,8 +29,6 @@ import kotlinx.coroutines.launch DeletedGoalWithUsageEntity::class, DeletedUsageEntity::class, LockWithDateEntity::class, - UserAuth::class, - UserProfile::class ], version = 2, exportSchema = false @@ -45,8 +39,6 @@ abstract class HMHRoomDatabase : RoomDatabase() { abstract fun challengeDao(): ChallengeDao abstract fun deletedGoalsDao(): DeletedGoalsDao abstract fun lockDao(): LockDao - abstract fun userAuthDao(): UserAuthDao - abstract fun userProfileDao(): UserProfileDao @OptIn(DelicateCoroutinesApi::class) fun deleteAll() { @@ -56,11 +48,9 @@ abstract class HMHRoomDatabase : RoomDatabase() { challengeDao().deleteAll() deletedGoalsDao().deleteAll() lockDao().deleteAll() - userAuthDao().clearUserAuth() - userProfileDao().clearUserProfile() } } - + companion object { // 버전 1에서 버전 2로 마이그레이션 val MIGRATION_1_2 = object : Migration(1, 2) { @@ -78,7 +68,7 @@ abstract class HMHRoomDatabase : RoomDatabase() { ) """ ) - + // UserProfile 테이블 생성 database.execSQL( """ diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt deleted file mode 100644 index ea540f51e..000000000 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserAuthDao.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.hmh.hamyeonham.core.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.hmh.hamyeonham.core.database.entity.UserAuth -import kotlinx.coroutines.flow.Flow - -@Dao -interface UserAuthDao { - @Query("SELECT * FROM user_auth WHERE id = 1") - suspend fun getUserAuth(): UserAuth? - - @Query("SELECT * FROM user_auth WHERE id = 1") - fun observeUserAuth(): Flow - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertOrUpdateUserAuth(userAuth: UserAuth) - - @Query("UPDATE user_auth SET isLoggedIn = :isLoggedIn WHERE id = 1") - suspend fun updateLoginStatus(isLoggedIn: Boolean) - - @Query("DELETE FROM user_auth") - suspend fun clearUserAuth() -} \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt deleted file mode 100644 index 24f4c3777..000000000 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/dao/UserProfileDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.hmh.hamyeonham.core.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.hmh.hamyeonham.core.database.entity.UserProfile -import kotlinx.coroutines.flow.Flow - -@Dao -interface UserProfileDao { - @Query("SELECT * FROM user_profile WHERE userId = :userId") - suspend fun getUserProfile(userId: Long): UserProfile? - - @Query("SELECT * FROM user_profile WHERE userId = :userId") - fun observeUserProfile(userId: Long): Flow - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertOrUpdateUserProfile(userProfile: UserProfile) - - @Query("DELETE FROM user_profile") - suspend fun clearUserProfile() -} \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt index 430855157..40bf7e28b 100644 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt +++ b/core/database/src/main/java/com/hmh/hamyeonham/core/database/di/DaoModule.kt @@ -39,16 +39,4 @@ object DaoModule { fun providesLockDao( database: HMHRoomDatabase, ) = database.lockDao() - - @Provides - @Singleton - fun providesUserAuthDao( - database: HMHRoomDatabase, - ) = database.userAuthDao() - - @Provides - @Singleton - fun providesUserProfileDao( - database: HMHRoomDatabase, - ) = database.userProfileDao() } diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt deleted file mode 100644 index 12394e269..000000000 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserAuth.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.hmh.hamyeonham.core.database.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey - -/** - * 사용자 인증 정보를 저장하는 엔티티 - * 토큰은 SDK에서 관리하므로 저장하지 않음 - */ -@Entity(tableName = "user_auth") -data class UserAuth( - @PrimaryKey - val id: Int = 1, // 항상 같은 ID 사용 (싱글 인스턴스) - val userId: Long = -1, - val providerType: String = "", // "KAKAO", "GOOGLE" 등 AuthProvider의 name - val isLoggedIn: Boolean = false, - val lastLoginTimestamp: Long = 0 -) \ No newline at end of file diff --git a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt b/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt deleted file mode 100644 index 6771b3d93..000000000 --- a/core/database/src/main/java/com/hmh/hamyeonham/core/database/entity/UserProfile.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.hmh.hamyeonham.core.database.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey - -/** - * 사용자 프로필 정보를 저장하는 엔티티 - */ -@Entity(tableName = "user_profile") -data class UserProfile( - @PrimaryKey - val userId: Long, - val nickname: String? = null, - val profileImageUrl: String? = null, - val email: String? = null, - val ageRange: String? = null, - val gender: String? = null, - val updatedAt: Long = System.currentTimeMillis() -) \ No newline at end of file diff --git a/core/designsystem/src/main/res/values/themes.xml b/core/designsystem/src/main/res/values/themes.xml index 513ac4064..de2756e27 100644 --- a/core/designsystem/src/main/res/values/themes.xml +++ b/core/designsystem/src/main/res/values/themes.xml @@ -1,4 +1,4 @@ - +