From aa8dac13027ce1900cf02b0956c78eca323f3214 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 03:43:37 -0400 Subject: [PATCH 01/11] KAN-111 [REFACTOR] Modify Local App Preference Repository Structure - Allow Dagger to manage Local AppPreferenceRepository as a Singleton instance, exposing its accessibility via interface in :domain module (Dependency Inversion) * Refactor AppPreference to AppPreferenceRepository and its Implementation. * Necessary Dependency has been provided via LocalPreferenceModule with @Singleton Annotation. --- .../com/doyoonkim/data/di/LocalModules.kt | 15 ++++++-- .../local/AppPreferenceRepositoryImpl.kt | 34 +++++++++---------- .../interfaces/AppPreferenceRepository.kt | 33 ++++++++++++++++++ 3 files changed, 62 insertions(+), 20 deletions(-) rename common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt => core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt (71%) create mode 100644 core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt diff --git a/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt b/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt index 318e5ee8..0d51a628 100644 --- a/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt +++ b/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt @@ -1,10 +1,12 @@ package com.doyoonkim.data.di import android.content.Context +import com.doyoonkim.data.repository.local.AppPreferenceRepositoryImpl import com.doyoonkim.model.di.ApplicationContext -import com.doyoonkim.data.repository.LocalRepositoryImpl -import com.doyoonkim.data.repository.LocalWidgetCacheRepositoryImpl +import com.doyoonkim.data.repository.local.LocalRepositoryImpl +import com.doyoonkim.data.repository.local.LocalWidgetCacheRepositoryImpl import com.doyoonkim.data.room.LocalDatabase +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.BookmarkLocalRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.interfaces.NoticeLocalRepository @@ -46,4 +48,13 @@ object RoomDatabaseModule { @Singleton fun provideMainDatabaseDao(db: LocalDatabase) = db.getDao() +} + +@Module +abstract class LocalPreferenceModule { + @Binds + @Singleton + abstract fun bindLocalAppPreferenceRepository( + impl: AppPreferenceRepositoryImpl + ): AppPreferenceRepository } \ No newline at end of file diff --git a/common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt similarity index 71% rename from common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt index 88408d40..1fea1686 100644 --- a/common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt @@ -1,15 +1,16 @@ -package com.doyoonkim.common.di +package com.doyoonkim.data.repository.local import android.content.SharedPreferences -import javax.inject.Inject import androidx.core.content.edit +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.model.WidgetCategoryPolicy import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import javax.inject.Inject -class AppPreferences @Inject constructor( +class AppPreferenceRepositoryImpl @Inject constructor( private val appPref: SharedPreferences -) { +) : AppPreferenceRepository { private val DB_SYNC_STATUS = "DB_SYNC_STATUS" private val DB_SYNC_PARTIAL_FAIL = "DB_SYNC_PARTIAL_FAIL" @@ -35,41 +36,41 @@ class AppPreferences @Inject constructor( * updateDeviceToken * @param token: Unique FCM token issued to this device. */ - fun updateDeviceToken(token: String) { + override fun updateDeviceToken(token: String) { appPref.edit { putString(DEVICE_TOKEN, token) } } - fun getCachedToken(): String? { + override fun getCachedToken(): String? { return appPref.getString(DEVICE_TOKEN, null) } /** * Major Subscription Status */ - fun getSubscribedMajor(): String? { + override fun getSubscribedMajor(): String? { return appPref.getString(SUBSCRIBED_MAJOR, null) } - fun updateSubscribedMajor(newMajor: String) { + override fun updateSubscribedMajor(newMajor: String) { appPref.edit { putString(SUBSCRIBED_MAJOR, newMajor) } } /** * Widget Configuration */ - fun getWidgetCategoryPolicy(): WidgetCategoryPolicy { + override fun getWidgetCategoryPolicy(): WidgetCategoryPolicy { val cachedPolicy = appPref.getString(WIDGET_CATEGORY, null) ?: return WidgetCategoryPolicy.Unconfigured return json.decodeFromString(cachedPolicy) } - fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) { + override fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) { val serializedString = json.encodeToString(policy) appPref.edit { putString(WIDGET_CATEGORY, serializedString) } } - fun isDatabaseSyncCompleted(): Boolean { + override fun isDatabaseSyncCompleted(): Boolean { // If sync 2_3 is required, return // appPref.getBoolean(DB_SYNC_1_2_STATUS, false) && appPref.getBoolean(DB_SYNC_2_3_STATUS, false) @@ -79,18 +80,15 @@ class AppPreferences @Inject constructor( } } - fun isPartialFailedDuringDatabaseSync() = appPref.getBoolean(DB_SYNC_PARTIAL_FAIL, false) - - fun setDatabaseSyncStatus(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_STATUS, status) } + override fun isPartialFailedDuringDatabaseSync() = appPref.getBoolean(DB_SYNC_PARTIAL_FAIL, false) - fun setSyncStatus_1_2(status: Boolean) = + override fun setSyncStatus_1_2(status: Boolean) = appPref.edit { putBoolean(DB_SYNC_1_2_STATUS, status) } - fun setSyncStatus_2_3(status: Boolean) = + override fun setSyncStatus_2_3(status: Boolean) = appPref.edit { putBoolean(DB_SYNC_2_3_STATUS, status) } - fun setDatabaseSyncPartialFailedStatus(status: Boolean) = + override fun setDatabaseSyncPartialFailedStatus(status: Boolean) = appPref.edit { putBoolean(DB_SYNC_PARTIAL_FAIL, status) } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt new file mode 100644 index 00000000..457f6bc8 --- /dev/null +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt @@ -0,0 +1,33 @@ +package com.doyoonkim.domain.interfaces + +import com.doyoonkim.model.WidgetCategoryPolicy + +/** + * @author kimdoyoon + * Created 3/25/26 at 1:42 AM + */ +interface AppPreferenceRepository { + + fun updateDeviceToken(token: String) + + fun getCachedToken(): String? + + fun getSubscribedMajor(): String? + + fun updateSubscribedMajor(newMajor: String) + + fun getWidgetCategoryPolicy(): WidgetCategoryPolicy + + fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) + + fun isDatabaseSyncCompleted(): Boolean + + fun isPartialFailedDuringDatabaseSync(): Boolean + + fun setSyncStatus_1_2(status: Boolean) + + fun setSyncStatus_2_3(status: Boolean) + + fun setDatabaseSyncPartialFailedStatus(status: Boolean) + +} \ No newline at end of file From c0e47213dd6fcda74131da0bd2dfbe80c7667b7e Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 03:46:59 -0400 Subject: [PATCH 02/11] KAN-111 [REFACTOR] Decentralized RemoteRepositoryImpl. - Decentralize 'RemoteRepositoryImpl' into three independent Repositories. 1. RemoteContentRepositoryImpl 2. RemotePreferenceRepositoryImpl 3. RemoteTokenRepositoryImpl * Necessary directory structure modification has been performed. --- .../com/doyoonkim/data/di/RemoteModules.kt | 18 +- .../data/repository/RemoteRepositoryImpl.kt | 234 ------------------ .../{ => local}/LocalRepositoryImpl.kt | 2 +- .../LocalWidgetCacheRepositoryImpl.kt | 6 +- .../{ => remote}/CampusRemoteRepository.kt | 10 +- .../{ => remote}/ImageRepositoryImpl.kt | 2 +- .../remote/RemoteContentRepositoryImpl.kt | 110 ++++++++ .../remote/RemotePreferenceRepository.kt | 94 +++++++ .../remote/RemoteTokenRepository.kt | 80 ++++++ 9 files changed, 306 insertions(+), 250 deletions(-) delete mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/RemoteRepositoryImpl.kt rename core/data/src/main/java/com/doyoonkim/data/repository/{ => local}/LocalRepositoryImpl.kt (99%) rename core/data/src/main/java/com/doyoonkim/data/repository/{ => local}/LocalWidgetCacheRepositoryImpl.kt (90%) rename core/data/src/main/java/com/doyoonkim/data/repository/{ => remote}/CampusRemoteRepository.kt (83%) rename core/data/src/main/java/com/doyoonkim/data/repository/{ => remote}/ImageRepositoryImpl.kt (95%) create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt diff --git a/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt b/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt index a5725ce8..8c67cdea 100644 --- a/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt +++ b/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt @@ -1,8 +1,10 @@ package com.doyoonkim.data.di -import com.doyoonkim.data.repository.CampusRemoteRepository -import com.doyoonkim.data.repository.ImageRepositoryImpl -import com.doyoonkim.data.repository.RemoteRepositoryImpl +import com.doyoonkim.data.repository.remote.CampusRemoteRepository +import com.doyoonkim.data.repository.remote.ImageRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteContentRepositoryImpl +import com.doyoonkim.data.repository.remote.RemotePreferenceRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteTokenRepositoryImpl import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.domain.interfaces.ImageRemoteRepository import com.doyoonkim.domain.interfaces.NoticeRemoteRepository @@ -17,7 +19,7 @@ import dagger.Module abstract class NoticeRemoteModule { @Binds abstract fun bindsNoticeRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteContentRepositoryImpl ) : NoticeRemoteRepository } @@ -25,7 +27,7 @@ abstract class NoticeRemoteModule { abstract class TipRemoteModule { @Binds abstract fun bindsTipRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteContentRepositoryImpl ) : TipRemoteRepository } @@ -33,7 +35,7 @@ abstract class TipRemoteModule { abstract class TokenRemoteModule { @Binds abstract fun bindsTokenRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteTokenRepositoryImpl ) : TokenRemoteRepository } @@ -41,12 +43,12 @@ abstract class TokenRemoteModule { abstract class PreferencesRemoteModule { @Binds abstract fun bindsTopicSubscriptionRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemotePreferenceRepositoryImpl ) : TopicSubscriptionRemoteRepository @Binds abstract fun bindsUserReportRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemotePreferenceRepositoryImpl ) : UserReportRemoteRepository } diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/RemoteRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/RemoteRepositoryImpl.kt deleted file mode 100644 index df745823..00000000 --- a/core/data/src/main/java/com/doyoonkim/data/repository/RemoteRepositoryImpl.kt +++ /dev/null @@ -1,234 +0,0 @@ -package com.doyoonkim.data.repository - -import android.util.Log -import com.doyoonkim.domain.interfaces.NoticeRemoteRepository -import com.doyoonkim.domain.interfaces.TipRemoteRepository -import com.doyoonkim.domain.interfaces.TokenRemoteRepository -import com.doyoonkim.domain.interfaces.TopicSubscriptionRemoteRepository -import com.doyoonkim.domain.interfaces.UserReportRemoteRepository -import com.doyoonkim.model.NoticeVO -import com.doyoonkim.model.TipVO -import com.doyoonkim.model.TokenStatus -import com.doyoonkim.model.TopicType -import com.doyoonkim.model.requestBody.DeviceTokenBody -import com.doyoonkim.model.requestBody.TokenUpdateBody -import com.doyoonkim.model.requestBody.TopicSubscriptionPreferencesBody -import com.doyoonkim.model.requestBody.UserReportBody -import com.doyoonkim.network.KnuticeRemoteSource -import com.doyoonkim.network.model.FcmTokenSaveRequest -import com.doyoonkim.network.model.FcmTokenUpdateRequest -import com.doyoonkim.network.model.ReportSaveRequest -import com.doyoonkim.network.model.TopicUpdateRequest -import kotlinx.coroutines.flow.flow -import model.Metadata -import java.net.ConnectException -import java.net.SocketTimeoutException -import javax.inject.Inject - -class RemoteRepositoryImpl @Inject constructor( - private val remoteSource: KnuticeRemoteSource -) : NoticeRemoteRepository, - TokenRemoteRepository, - TopicSubscriptionRemoteRepository, - UserReportRemoteRepository, - TipRemoteRepository -{ - private val TAG = "RemoteRepositoryImpl" - - override suspend fun queryTopThreeNotices(category: String): List? { - remoteSource.getNoticesPerPage(category = category, size = 3).fold( - onSuccess = { - if (it.result?.resultCode == 200) return it.body?.map { it.toVO() } - else it.result?.printLog().also { return emptyList() } - }, - onFailure = { - it.printLog() - return null - } - ) - return null - } - - - - override fun queryNoticesPerPage(category: String, lastNttId: Int?) = flow { - remoteSource.getNoticesPerPage(category = category, lastNttId = lastNttId).fold( - onSuccess = { - if (it.result?.resultCode == 200) emit(it.body?.map { it.toVO() }) - else it.result.printLog().also { emit(emptyList()) } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - - override fun queryNoticeById(nttId: Int) = flow { - remoteSource.getNoticeById(nttId).fold( - onSuccess = { - Log.d(TAG, "Received Result: ${it.toString()}") - if (it.result?.resultCode == 200) emit(it.body?.toVO()) - else it.result.printLog().also { emit(null) } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - - override fun queryNoticesByKeyword(keyword: String, lastNttId: Int?) = flow { - remoteSource.getNoticesByKeyword(keyword, lastNttId).fold( - onSuccess = { - if (it.result?.resultCode == 200) emit(it.body?.map { it.toVO() }) - else it.result.printLog().also { emit(emptyList()) } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - - override suspend fun getNoticeSummary(nttId: Int): String? { - remoteSource.getNoticeSummary(nttId).fold( - onSuccess = { - if (it.result?.resultCode == 200) return it.body?.rawSummary - else it.result.printLog().also { return null } - }, - onFailure = { - it.printLog() - return null - } - ) - return null - } - - override fun queryTopicSubscriptionStatus(topicType: TopicType) = flow { - remoteSource.getTopicSubscriptionStatus(topicType.name).fold( - onSuccess = { - Log.d(TAG, it.toString()) - if (it.result?.resultCode == 200) emit(it.body?.toVO(topicType)) - else { - if (it.body != null) emit(it.body?.toVO(topicType)) - else it.result.printLog().also { emit(null) } - } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - - override fun queryAllTips() = flow { - remoteSource.getAllTips().fold( - onSuccess = { - if (it.result?.resultCode == 200) { - emit(it.body?.map { dto -> dto.toVO() }) - } else it.result.printLog().also { emit(emptyList()) } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - - override suspend fun requestUpdateFcmToken(body: TokenUpdateBody): TokenStatus { - remoteSource.updateDeviceToken( - newToken = body.newFcmToken, - request = FcmTokenUpdateRequest( - oldFcmToken = body.oldFcmToken, - deviceType = body.deviceType - ) - ).fold( - onSuccess = { - if (it.result?.resultCode == 200) return TokenStatus.SUCCESS - else it.result.printLog().also { return TokenStatus.RETRY } - }, - onFailure = { - it.printLog() - return when(it) { - is ConnectException -> TokenStatus.RETRY - is SocketTimeoutException -> TokenStatus.RETRY - else -> TokenStatus.FAILURE - } - } - ) - // Default - return TokenStatus.FAILURE - } - - override suspend fun requestFcmTokenRegistration(body: DeviceTokenBody): TokenStatus { - remoteSource.validateToken( - token = body.fcmToken, - request = FcmTokenSaveRequest(body.deviceType) - ).fold( - onSuccess = { - if (it.result?.resultCode == 200) return TokenStatus.SUCCESS - else it.result.printLog().also { return TokenStatus.RETRY } - }, - onFailure = { - it.printLog() - when(it) { - is ConnectException -> return TokenStatus.RETRY - is SocketTimeoutException -> return TokenStatus.RETRY - else -> return TokenStatus.FAILURE - } - } - ) - // Default - return TokenStatus.FAILURE - } - - override fun requestUserReportSubmission(body: UserReportBody) = flow { - remoteSource.submitUserReport( - ReportSaveRequest( - content = body.content, - deviceName = body.deviceName, - version = body.version - ) - ).fold( - onSuccess = { - if (it.result?.resultCode == 200) emit(true) - else it.result.printLog().also { emit(false) } - }, - onFailure = { - it.printLog() - emit(false) - } - ) - } - - override fun requestTopicSubscriptionPreferencesSubmission( - body: TopicSubscriptionPreferencesBody - ) = flow { - remoteSource.run { - submitTopicSubscriptionPreferences( - type = body.topicType, - request = TopicUpdateRequest( - topic = body.noticeName, - enabled = body.isSubscribed - ) - ) - }.fold( - onSuccess = { - if (it.result?.resultCode == 200) emit(true) - else it.result.printLog().also { emit(false) } - }, - onFailure = { - it.printLog() - emit(false) - } - ) - } - - private fun Throwable.printLog() = - Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") - - private fun Metadata?.printLog() = - Log.d(TAG, "Failed to receive data (${this?.resultCode})" + - "\nREASON:${this?.resultMessage}") -} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/LocalRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalRepositoryImpl.kt similarity index 99% rename from core/data/src/main/java/com/doyoonkim/data/repository/LocalRepositoryImpl.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/local/LocalRepositoryImpl.kt index dd13c0cc..e53d9202 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/LocalRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalRepositoryImpl.kt @@ -1,4 +1,4 @@ -package com.doyoonkim.data.repository +package com.doyoonkim.data.repository.local import android.util.Log import androidx.room.withTransaction diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/LocalWidgetCacheRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt similarity index 90% rename from core/data/src/main/java/com/doyoonkim/data/repository/LocalWidgetCacheRepositoryImpl.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt index 9692cfad..ac85bab8 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/LocalWidgetCacheRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt @@ -1,7 +1,7 @@ -package com.doyoonkim.data.repository +package com.doyoonkim.data.repository.local import android.util.Log -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.model.CarrelRoomStatusVO import com.doyoonkim.model.NoticeVO @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.update import javax.inject.Inject class LocalWidgetCacheRepositoryImpl @Inject constructor( - appPreference: AppPreferences + appPreference: AppPreferenceRepository ) : LocalWidgetCacheRepository { companion object { diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/CampusRemoteRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt similarity index 83% rename from core/data/src/main/java/com/doyoonkim/data/repository/CampusRemoteRepository.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt index 14200074..afedf59e 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/CampusRemoteRepository.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt @@ -1,6 +1,7 @@ -package com.doyoonkim.data.repository +package com.doyoonkim.data.repository.remote import android.util.Log +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.model.CarrelRoomStatusVO import com.doyoonkim.network.KnuticeRemoteSource @@ -10,14 +11,17 @@ import javax.inject.Inject class CampusRemoteRepository @Inject constructor( - private val remoteSource: KnuticeRemoteSource + private val remoteSource: KnuticeRemoteSource, + private val appPreference: AppPreferenceRepository ): CarrelStatusRemoteRepository { companion object { private val TAG = "CampusRemoteRepository" } override suspend fun getCarrelRoomStatus(): Result> { - remoteSource.getCarrelRoomStatus().fold( + remoteSource.getCarrelRoomStatus( + appPreference.getCachedToken() + ).fold( onSuccess = { response -> if (response.result == null) Log.d(TAG, "Unable to receive data").also { diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/ImageRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/ImageRepositoryImpl.kt similarity index 95% rename from core/data/src/main/java/com/doyoonkim/data/repository/ImageRepositoryImpl.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/ImageRepositoryImpl.kt index fe19c178..707ae2e0 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/ImageRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/ImageRepositoryImpl.kt @@ -1,4 +1,4 @@ -package com.doyoonkim.data.repository +package com.doyoonkim.data.repository.remote import android.util.Log import com.doyoonkim.domain.interfaces.ImageRemoteRepository diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt new file mode 100644 index 00000000..4cc20ba1 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt @@ -0,0 +1,110 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.NoticeRemoteRepository +import com.doyoonkim.domain.interfaces.TipRemoteRepository +import com.doyoonkim.model.NoticeVO +import com.doyoonkim.model.TipVO +import com.doyoonkim.network.KnuticeRemoteSource +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemoteContentRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource +) : NoticeRemoteRepository, + TipRemoteRepository +{ + private val TAG = "RemoteRepositoryImpl" + + override suspend fun queryTopThreeNotices(category: String): List? { + remoteSource.getNoticesPerPage(category = category, size = 3).fold( + onSuccess = { + if (it.result?.resultCode == 200) return it.body?.map { it.toVO() } + else it.result?.printLog().also { return emptyList() } + }, + onFailure = { + it.printLog() + return null + } + ) + return null + } + + + + override fun queryNoticesPerPage(category: String, lastNttId: Int?) = flow { + remoteSource.getNoticesPerPage(category = category, lastNttId = lastNttId).fold( + onSuccess = { + if (it.result?.resultCode == 200) emit(it.body?.map { it.toVO() }) + else it.result.printLog().also { emit(emptyList()) } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + override fun queryNoticeById(nttId: Int) = flow { + remoteSource.getNoticeById(nttId).fold( + onSuccess = { + Log.d(TAG, "Received Result: ${it.toString()}") + if (it.result?.resultCode == 200) emit(it.body?.toVO()) + else it.result.printLog().also { emit(null) } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + override fun queryNoticesByKeyword(keyword: String, lastNttId: Int?) = flow { + remoteSource.getNoticesByKeyword(keyword, lastNttId).fold( + onSuccess = { + if (it.result?.resultCode == 200) emit(it.body?.map { it.toVO() }) + else it.result.printLog().also { emit(emptyList()) } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + override suspend fun getNoticeSummary(nttId: Int): String? { + remoteSource.getNoticeSummary(nttId).fold( + onSuccess = { + if (it.result?.resultCode == 200) return it.body?.rawSummary + else it.result.printLog().also { return null } + }, + onFailure = { + it.printLog() + return null + } + ) + return null + } + + override fun queryAllTips() = flow { + remoteSource.getAllTips().fold( + onSuccess = { + if (it.result?.resultCode == 200) { + emit(it.body?.map { dto -> dto.toVO() }) + } else it.result.printLog().also { emit(emptyList()) } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + private fun Throwable.printLog() = + Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") + + private fun Metadata?.printLog() = + Log.d(TAG, "Failed to receive data (${this?.resultCode})" + + "\nREASON:${this?.resultMessage}") +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt new file mode 100644 index 00000000..e7bd1021 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt @@ -0,0 +1,94 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.TopicSubscriptionRemoteRepository +import com.doyoonkim.domain.interfaces.UserReportRemoteRepository +import com.doyoonkim.model.TopicType +import com.doyoonkim.model.requestBody.TopicSubscriptionPreferencesBody +import com.doyoonkim.model.requestBody.UserReportBody +import com.doyoonkim.network.KnuticeRemoteSource +import com.doyoonkim.network.model.ReportSaveRequest +import com.doyoonkim.network.model.TopicUpdateRequest +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemotePreferenceRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource, + private val appPreference: AppPreferenceRepository +) : TopicSubscriptionRemoteRepository, UserReportRemoteRepository { + + private val TAG = "RemotePreferenceRepository" + + override fun queryTopicSubscriptionStatus(topicType: TopicType) = flow { + remoteSource.getTopicSubscriptionStatus( + appPreference.getCachedToken(), + topicType.name + ).fold( + onSuccess = { + Log.d(TAG, it.toString()) + if (it.result?.resultCode == 200) emit(it.body?.toVO(topicType)) + else { + if (it.body != null) emit(it.body?.toVO(topicType)) + else it.result.printLog().also { emit(null) } + } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + override fun requestTopicSubscriptionPreferencesSubmission( + body: TopicSubscriptionPreferencesBody + ) = flow { + remoteSource.run { + submitTopicSubscriptionPreferences( + token = appPreference.getCachedToken(), + type = body.topicType, + request = TopicUpdateRequest( + topic = body.noticeName, + enabled = body.isSubscribed + ) + ) + }.fold( + onSuccess = { + if (it.result?.resultCode == 200) emit(true) + else it.result.printLog().also { emit(false) } + }, + onFailure = { + it.printLog() + emit(false) + } + ) + } + + override fun requestUserReportSubmission(body: UserReportBody) = flow { + remoteSource.submitUserReport( + appPreference.getCachedToken(), + ReportSaveRequest( + content = body.content, + deviceName = body.deviceName, + version = body.version + ) + ).fold( + onSuccess = { + if (it.result?.resultCode == 200) emit(true) + else it.result.printLog().also { emit(false) } + }, + onFailure = { + it.printLog() + emit(false) + } + ) + } + + private fun Throwable.printLog() = + Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") + + private fun Metadata?.printLog() = + Log.d(TAG, "Failed to receive data (${this?.resultCode})" + + "\nREASON:${this?.resultMessage}") +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt new file mode 100644 index 00000000..09151568 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt @@ -0,0 +1,80 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.TokenRemoteRepository +import com.doyoonkim.model.TokenStatus +import com.doyoonkim.model.requestBody.DeviceTokenBody +import com.doyoonkim.model.requestBody.TokenUpdateBody +import com.doyoonkim.network.KnuticeRemoteSource +import com.doyoonkim.network.model.FcmTokenSaveRequest +import com.doyoonkim.network.model.FcmTokenUpdateRequest +import model.Metadata +import java.net.ConnectException +import java.net.SocketTimeoutException +import javax.inject.Inject + +class RemoteTokenRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource, + private val appPreference: AppPreferenceRepository +) : TokenRemoteRepository { + + private val TAG = "RemoteTokenRepository" + + override suspend fun requestUpdateFcmToken(body: TokenUpdateBody): TokenStatus { + remoteSource.updateDeviceToken( + cachedToken = appPreference.getCachedToken(), + newToken = body.newFcmToken, + request = FcmTokenUpdateRequest( + oldFcmToken = body.oldFcmToken, + deviceType = body.deviceType + ) + ).fold( + onSuccess = { + if (it.result?.resultCode == 200) return TokenStatus.SUCCESS + else it.result.printLog().also { return TokenStatus.RETRY } + }, + onFailure = { + it.printLog() + return when(it) { + is ConnectException -> TokenStatus.RETRY + is SocketTimeoutException -> TokenStatus.RETRY + else -> TokenStatus.FAILURE + } + } + ) + // Default + return TokenStatus.FAILURE + } + + override suspend fun requestFcmTokenRegistration(body: DeviceTokenBody): TokenStatus { + remoteSource.validateToken( + cachedToken = appPreference.getCachedToken(), + receivedToken = body.fcmToken, + request = FcmTokenSaveRequest(body.deviceType) + ).fold( + onSuccess = { + if (it.result?.resultCode == 200) return TokenStatus.SUCCESS + else it.result.printLog().also { return TokenStatus.RETRY } + }, + onFailure = { + it.printLog() + return when(it) { + is ConnectException -> TokenStatus.RETRY + is SocketTimeoutException -> TokenStatus.RETRY + else -> TokenStatus.FAILURE + } + } + ) + // Default + return TokenStatus.FAILURE + } + + private fun Throwable.printLog() = + Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") + + private fun Metadata?.printLog() = + Log.d(TAG, "Failed to receive data (${this?.resultCode})" + + "\nREASON:${this?.resultMessage}") + +} \ No newline at end of file From 5561a73d272b4c8a5160b0f0798d90108781edeb Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 03:50:18 -0400 Subject: [PATCH 03/11] KAN-111 [REFACTOR] Modify Local App Preference Repository Structure - Provide dependency of AppPreferenceRepository instance as a Singleton. * Dependency is provided via Component-Dependency between AppComponent and each SceneComponents. --- .../com/doyoonkim/knutice/MainApplication.kt | 4 +- .../com/doyoonkim/knutice/SplashActivity.kt | 1 + .../knutice/WidgetConfigurationActivity.kt | 3 +- .../knutice/di/components/AppComponent.kt | 8 +-- .../NotificationServiceComponent.kt | 5 +- .../knutice/di/components/SceneComponents.kt | 58 ++++++++++++++----- .../knutice/di/components/WidgetComponent.kt | 5 +- .../knutice/di/util/DefaultSystemService.kt | 5 ++ .../knutice/navigation/MainServiceNavGraph.kt | 9 ++- .../navigation/bookmarkServiceGraph.kt | 1 + 10 files changed, 74 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt b/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt index 1f72aa7c..0dbc0919 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt @@ -31,7 +31,7 @@ class MainApplication() : Application(), AppInjectorProvider, WidgetDependencyPr when(target) { is PushNotificationService -> { DaggerNotificationServiceComponent.factory() - .create(appComponent, appComponent).inject(target) + .create(appComponent, appComponent, appComponent).inject(target) } else -> error("Unsupported Target $target") } @@ -41,7 +41,7 @@ class MainApplication() : Application(), AppInjectorProvider, WidgetDependencyPr override fun provide(): WidgetDependency { - return DaggerWidgetComponent.factory().create(appComponent, appComponent) + return DaggerWidgetComponent.factory().create(appComponent, appComponent, appComponent) } @Inject lateinit var notificationManager: NotificationManager diff --git a/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt b/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt index 581bdc9b..2cd48d13 100644 --- a/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt @@ -49,6 +49,7 @@ class SplashActivity : ComponentActivity() { systemServices = appComponent, networkProvider = appComponent, localStorageProvider = appComponent, + localCacheProvider = appComponent, firebaseInfrastructureProvider = appComponent ) diff --git a/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt b/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt index d245bdf7..3faabf9a 100644 --- a/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt @@ -40,7 +40,8 @@ class WidgetConfigurationActivity : ComponentActivity() { // Dependency Injection val appComponent = (application as MainApplication).appComponent val sceneComponent = DaggerWidgetConfigSceneComponent.factory().create( - systemService = appComponent + systemService = appComponent, + localCacheProvider = appComponent ) WidgetPreferencesScreen( diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt index bb61934b..1bcaefc0 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt @@ -2,7 +2,6 @@ package com.doyoonkim.knutice.di.components import android.app.Application import androidx.work.ListenableWorker -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.data.di.LocalModule import com.doyoonkim.data.di.TokenRemoteModule import com.doyoonkim.domain.di.AsyncFtsEntryInsertionModule @@ -15,12 +14,14 @@ import com.doyoonkim.network.di.NetworkModule import com.doyoonkim.notification.di.FcmTokenModule import com.doyoonkim.common.worker.IntermediateWorkerFactory import com.doyoonkim.data.di.CampusRemoteModule +import com.doyoonkim.data.di.LocalPreferenceModule import com.doyoonkim.data.di.NoticeRemoteModule import com.doyoonkim.data.di.RoomDatabaseModule import com.doyoonkim.data.di.SystemCoroutineModule import com.doyoonkim.infrastructure.di.FirebaseAnalyticsModule import com.doyoonkim.infrastructure.di.FirebaseRemoteConfigModule import com.doyoonkim.knutice.di.util.FirebaseInfrastructureProvider +import com.doyoonkim.knutice.di.util.LocalCacheProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -41,6 +42,7 @@ import javax.inject.Singleton SystemCoroutineModule::class, LocalModule::class, RoomDatabaseModule::class, + LocalPreferenceModule::class, NetworkModule::class ] ) @@ -48,13 +50,11 @@ interface AppComponent : SystemServices, NetworkProvider, LocalStorageProvider, + LocalCacheProvider, FirebaseInfrastructureProvider { fun inject(app: MainApplication) - // AppPref Instance - fun appPreference(): AppPreferences - // Worker Subcomponent fun workerComponent(): WorkerSubcomponent.Factory diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt index de3810da..f67cb86a 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt @@ -5,6 +5,7 @@ import com.doyoonkim.data.di.ImageRemoteModule import com.doyoonkim.data.di.TokenRemoteModule import com.doyoonkim.domain.di.TokenUseCaseModule import com.doyoonkim.knutice.di.modules.WorkSchedulerModule +import com.doyoonkim.knutice.di.util.LocalCacheProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices import com.doyoonkim.notification.di.NotificationModule @@ -14,6 +15,7 @@ import dagger.Component @Component( dependencies = [ SystemServices::class, + LocalCacheProvider::class, NetworkProvider::class ], modules = [ @@ -32,7 +34,8 @@ interface NotificationServiceComponent { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + localCacheProvider: LocalCacheProvider ): NotificationServiceComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt index a4338b60..e08ec123 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt @@ -16,6 +16,7 @@ import com.doyoonkim.domain.di.TipUseCaseModule import com.doyoonkim.knutice.di.modules.ViewModelFactoryModule import com.doyoonkim.knutice.di.modules.WorkSchedulerModule import com.doyoonkim.knutice.di.util.FirebaseInfrastructureProvider +import com.doyoonkim.knutice.di.util.LocalCacheProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -37,7 +38,7 @@ import javax.inject.Singleton @Component( dependencies = [ SystemServices::class, - LocalStorageProvider::class, + LocalCacheProvider::class, NetworkProvider::class, FirebaseInfrastructureProvider::class ], @@ -58,14 +59,18 @@ interface HomeSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider, + localCacheProvider: LocalCacheProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): HomeSceneComponent } } @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + NetworkProvider::class, + LocalCacheProvider::class + ], modules = [ ViewModelFactoryModule::class, NoticeByMajorSceneModule::class, @@ -82,14 +87,19 @@ interface NoticeByMajorSceneComponent { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + localCacheProvider: LocalCacheProvider ): NoticeByMajorSceneComponent } } @Component( - dependencies = [SystemServices::class, LocalStorageProvider::class], + dependencies = [ + SystemServices::class, + LocalStorageProvider::class, + LocalCacheProvider::class + ], modules = [ ViewModelFactoryModule::class, BookmarkListSceneModule::class, @@ -103,6 +113,7 @@ interface BookmarkListSceneComponent { interface Factory { fun create( systemServices: SystemServices, + localCacheProvider: LocalCacheProvider, localStorageProvider: LocalStorageProvider ): BookmarkListSceneComponent } @@ -212,7 +223,11 @@ interface NoticeInCategorySceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + LocalCacheProvider::class, + NetworkProvider::class + ], modules = [ ViewModelFactoryModule::class, CustomerServiceSceneModule::class, @@ -227,6 +242,7 @@ interface CustomerServiceSceneComponent { interface Factory { fun create( systemService: SystemServices, + localCacheProvider: LocalCacheProvider, networkProvider: NetworkProvider ): CustomerServiceSceneComponent } @@ -234,7 +250,11 @@ interface CustomerServiceSceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + LocalCacheProvider::class, + NetworkProvider::class + ], modules = [ ViewModelFactoryModule::class, NotificationPreferencesSceneModule::class, @@ -249,6 +269,7 @@ interface NotificationPreferencesSceneComponent { interface Factory { fun create( systemServices: SystemServices, + localCacheProvider: LocalCacheProvider, networkProvider: NetworkProvider ): NotificationPreferencesSceneComponent } @@ -256,7 +277,12 @@ interface NotificationPreferencesSceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class, LocalStorageProvider::class ], + dependencies = [ + SystemServices::class, + NetworkProvider::class, + LocalStorageProvider::class, + LocalCacheProvider::class + ], modules = [ ViewModelFactoryModule::class, SettingsSceneModule::class, @@ -272,7 +298,8 @@ interface SettingsSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider + localStorageProvider: LocalStorageProvider, + localCacheProvider: LocalCacheProvider ): SettingsSceneComponent } } @@ -283,6 +310,7 @@ interface SettingsSceneComponent { SystemServices::class, NetworkProvider::class, LocalStorageProvider::class, + LocalCacheProvider::class, FirebaseInfrastructureProvider::class ], modules = [ @@ -303,13 +331,14 @@ interface SplashSceneComponent { systemServices: SystemServices, networkProvider: NetworkProvider, localStorageProvider: LocalStorageProvider, + localCacheProvider: LocalCacheProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): SplashSceneComponent } } @Component( - dependencies = [SystemServices::class], + dependencies = [SystemServices::class, LocalCacheProvider::class], modules = [ ViewModelFactoryModule::class, WidgetConfigSceneModule::class, @@ -322,7 +351,10 @@ interface WidgetConfigSceneComponent { @Component.Factory interface Factory { - fun create(systemService: SystemServices): WidgetConfigSceneComponent + fun create( + systemService: SystemServices, + localCacheProvider: LocalCacheProvider + ): WidgetConfigSceneComponent } } @@ -330,7 +362,7 @@ interface WidgetConfigSceneComponent { dependencies = [ SystemServices::class, NetworkProvider::class, - LocalStorageProvider::class + LocalCacheProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -346,7 +378,7 @@ interface CarrelStatusSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider + localCacheProvider: LocalCacheProvider ): CarrelStatusSceneComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt index f1f6006f..75d43f3d 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt @@ -1,6 +1,7 @@ package com.doyoonkim.knutice.di.components import com.doyoonkim.knutice.di.modules.WorkSchedulerModule +import com.doyoonkim.knutice.di.util.LocalCacheProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices import com.doyoonkim.widget.di.WidgetDependency @@ -9,6 +10,7 @@ import dagger.Component @Component( dependencies = [ SystemServices::class, + LocalCacheProvider::class, NetworkProvider::class ], modules = [ @@ -21,7 +23,8 @@ interface WidgetComponent: WidgetDependency { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + localCacheProvider: LocalCacheProvider ): WidgetComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt b/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt index 2cb6b19b..35a93e52 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.SharedPreferences import androidx.work.WorkManager import com.doyoonkim.common.analytics.AnalyticsLogger +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.BookmarkLocalRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.interfaces.NoticeLocalRepository @@ -44,6 +45,10 @@ interface NetworkProvider { interface LocalStorageProvider { fun localNoticeRepository(): NoticeLocalRepository fun localBookmarkRepository(): BookmarkLocalRepository +} + +interface LocalCacheProvider { + fun localAppPreferenceRepository(): AppPreferenceRepository fun localWidgetCacheRepository(): LocalWidgetCacheRepository } diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt index 8da9c3a8..ee3888ec 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.core.EaseIn import androidx.compose.animation.core.EaseOut import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.remember @@ -63,7 +62,7 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerHomeSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localStorageProvider = appComponent, + localCacheProvider = appComponent, firebaseInfrastructureProvider = appComponent ) } @@ -108,7 +107,8 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerNoticeByMajorSceneComponent.factory().create( systemServices = appComponent, - networkProvider = appComponent + networkProvider = appComponent, + localCacheProvider = appComponent ) } @@ -336,6 +336,7 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerSettingsSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, + localCacheProvider = appComponent, localStorageProvider = appComponent ) } @@ -374,6 +375,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerNotificationPreferencesSceneComponent.factory().create( systemServices = appComponent, + localCacheProvider = appComponent, networkProvider = appComponent ) } @@ -403,6 +405,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerCustomerServiceSceneComponent.factory().create( systemService = appComponent, + localCacheProvider = appComponent, networkProvider = appComponent ) } diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt index ff0909aa..987421b7 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt @@ -36,6 +36,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( val sceneComponent = remember(appComponent) { DaggerBookmarkListSceneComponent.factory().create( systemServices = appComponent, + localCacheProvider = appComponent, localStorageProvider = appComponent ) } From 7f94d9e4c31935e704ac3da553e1faa980e80352 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 03:51:19 -0400 Subject: [PATCH 04/11] KAN-111 [REFACTOR] Modify Local App Preference Repository Structure - Replace legacy 'AppPreference' dependency to updated 'AppPreferenceRepository' --- .../doyoonkim/knutice/WidgetSyncObserver.kt | 4 +- .../doyoonkim/network/KnuticeRemoteSource.kt | 40 +++++++++---------- .../notification/fcm/TokenHandler.kt | 6 +-- .../viewmodel/BookmarkListViewModel.kt | 4 +- .../main/viewmodel/CarrelStatusViewModel.kt | 4 +- .../viewmodel/CustomerServiceViewModel.kt | 4 +- .../doyoonkim/main/viewmodel/HomeViewModel.kt | 4 +- .../main/viewmodel/NoticeByMajorViewModel.kt | 4 +- .../NotificationPreferencesViewModel.kt | 5 +-- .../main/viewmodel/SettingsViewModel.kt | 6 +-- .../main/viewmodel/SplashViewModel.kt | 20 ++++------ .../main/viewmodel/WidgetConfigViewModel.kt | 4 +- .../widget/worker/KnuticeWidgetSync.kt | 13 ++---- 13 files changed, 49 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt b/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt index 3e5a4736..4e593178 100644 --- a/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt +++ b/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt @@ -3,7 +3,7 @@ package com.doyoonkim.knutice import android.util.Log import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.model.WidgetCategoryPolicy import com.doyoonkim.widget.model.CarrelWidgetState @@ -17,7 +17,7 @@ import javax.inject.Inject class WidgetSyncObserver @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val widgetStateUpdater: WidgetStateUpdater, - private val appPreference: AppPreferences, + private val appPreference: AppPreferenceRepository, private val applicationScope: CoroutineScope ): DefaultLifecycleObserver { diff --git a/core/network/src/main/kotlin/com/doyoonkim/network/KnuticeRemoteSource.kt b/core/network/src/main/kotlin/com/doyoonkim/network/KnuticeRemoteSource.kt index 43de1b27..6cad43c5 100644 --- a/core/network/src/main/kotlin/com/doyoonkim/network/KnuticeRemoteSource.kt +++ b/core/network/src/main/kotlin/com/doyoonkim/network/KnuticeRemoteSource.kt @@ -1,7 +1,6 @@ package com.doyoonkim.network import android.util.Log -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.model.TopicType import com.doyoonkim.model.di.IoDispatcher import com.doyoonkim.network.model.FcmTokenSaveRequest @@ -21,7 +20,6 @@ import javax.inject.Inject // This class should be provided/injected as Singleton Instance. class KnuticeRemoteSource @Inject constructor( private val knuticeApi: KnuticeService, - private val appPreference: AppPreferences, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) { private val TAG = "KnuticeRemoteSource" @@ -61,13 +59,13 @@ class KnuticeRemoteSource @Inject constructor( } - suspend fun getTopicSubscriptionStatus(topicType: String) = + suspend fun getTopicSubscriptionStatus(token: String?, topicType: String) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank()) throw Exception("No validated token found") + if (token.isNullOrBlank()) throw Exception("No validated token found") else { knuticeApi.getTopicSubscriptionStatus( - appPreference.getCachedToken()!!, + token, topicType ) } @@ -81,58 +79,58 @@ class KnuticeRemoteSource @Inject constructor( } } - suspend fun getCarrelRoomStatus() = + suspend fun getCarrelRoomStatus(token: String?) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank()) throw Exception("No validated token found") + if (token.isNullOrBlank()) throw Exception("No validated token found") else { knuticeApi.getReadingRoomStatus( - appPreference.getCachedToken()!! + token ) } } } - suspend fun validateToken(token: String, request: FcmTokenSaveRequest) = + suspend fun validateToken(cachedToken: String?, receivedToken: String, request: FcmTokenSaveRequest) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank() || appPreference.getCachedToken() != token) { - throw Exception("Token Mismatch\nReceived: $token Cached: ${appPreference.getCachedToken()}") + if (cachedToken.isNullOrBlank() || cachedToken != receivedToken) { + throw Exception("Token Mismatch\nReceived: $receivedToken Cached: $cachedToken") } - knuticeApi.validateToken(appPreference.getCachedToken()!!, request) + knuticeApi.validateToken(cachedToken, request) } } - suspend fun submitUserReport(request: ReportSaveRequest) = + suspend fun submitUserReport(token: String?, request: ReportSaveRequest) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank()) throw Exception("No validated token found") + if (token.isNullOrBlank()) throw Exception("No validated token found") else { knuticeApi.submitUserReport( - appPreference.getCachedToken()!!, request + token, request ) } } } - suspend fun submitTopicSubscriptionPreferences(type: TopicType, request: TopicUpdateRequest) = + suspend fun submitTopicSubscriptionPreferences(token: String?, type: TopicType, request: TopicUpdateRequest) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank()) throw Exception("No validated token found") + if (token.isNullOrBlank()) throw Exception("No validated token found") knuticeApi.submitTopicSubscriptionPreferences( - appPreference.getCachedToken()!!, + token, type.name, request ) } } - suspend fun updateDeviceToken(newToken: String, request: FcmTokenUpdateRequest) = + suspend fun updateDeviceToken(cachedToken: String?, newToken: String, request: FcmTokenUpdateRequest) = withContext(ioDispatcher) { runCatching { - if (appPreference.getCachedToken().isNullOrBlank() || appPreference.getCachedToken() != newToken) { - throw Exception("Token mismatch\nReceived: $newToken Cached: ${appPreference.getCachedToken()}") + if (cachedToken.isNullOrBlank() || cachedToken != newToken) { + throw Exception("Token mismatch\nReceived: $newToken Cached: $cachedToken") } knuticeApi.updateFcmToken(newToken, request) } diff --git a/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt b/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt index e2a39aa9..7e1c7fcf 100644 --- a/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt +++ b/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt @@ -1,22 +1,20 @@ package com.doyoonkim.notification.fcm import android.util.Log -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.common.di.TokenHandler +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.ValidateDeviceToken import com.doyoonkim.model.TokenStatus import com.doyoonkim.model.requestBody.DeviceTokenBody import com.doyoonkim.model.requestBody.TokenUpdateBody import com.google.firebase.Firebase import com.google.firebase.messaging.messaging -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.tasks.await import javax.inject.Inject class TokenHandlerImpl @Inject constructor( private val validateDeviceToken: ValidateDeviceToken, - private val appPreferences: AppPreferences + private val appPreferences: AppPreferenceRepository ) : TokenHandler { private val TAG = this.javaClass.name diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt index 8017dc5c..8ab20dca 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt @@ -8,8 +8,8 @@ import com.doyoonkim.bookmark.contract.BookmarkListMutation import com.doyoonkim.bookmark.contract.BookmarkListSideEffect import com.doyoonkim.bookmark.contract.BookmarkListState import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.common.navigation.BookmarkInfo +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.FetchAllBookmarks import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @@ -17,7 +17,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class BookmarkListViewModel @Inject constructor( - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val fetchAllBookmarks: FetchAllBookmarks ) : BaseViewModel() { diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt index 32b0ce9f..4440bbae 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import kotlinx.coroutines.flow.MutableStateFlow @@ -15,7 +15,7 @@ import javax.inject.Inject class CarrelStatusViewModel @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val carrelStatusRepository: CarrelStatusRemoteRepository, - private val appPreference: AppPreferences + private val appPreference: AppPreferenceRepository ) : ViewModel() { // States diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt index 85d3f6a8..da7a2ef3 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.os.Build import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.SubmitUserReport import com.doyoonkim.main.contract.CustomerServiceEvent import com.doyoonkim.main.contract.CustomerServiceMutation @@ -16,7 +16,7 @@ import javax.inject.Inject class CustomerServiceViewModel @Inject constructor( private val submitUserReport: SubmitUserReport, - private val appPreferences: AppPreferences + private val appPreferences: AppPreferenceRepository ) : BaseViewModel() { override fun setInitialState(): CustomerServiceStatus = CustomerServiceStatus() diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt index 6279dee2..3605bc7b 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt @@ -5,8 +5,8 @@ import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.analytics.AnalyticsLogger import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.common.navigation.Destination +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.usecases.FetchTips import com.doyoonkim.domain.usecases.FetchTopThreeNotices @@ -30,7 +30,7 @@ class HomeViewModel @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val fetchTopThreeNotices: FetchTopThreeNotices, private val fetchTips: FetchTips, - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val analytics: AnalyticsLogger ) : BaseViewModel() { override fun setInitialState(): HomeViewState = HomeViewState() diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt index 7f5c3374..0c4cba01 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.FetchNoticesPerPage import com.doyoonkim.domain.usecases.SubmitNotificationPreferences import com.doyoonkim.main.contract.NoticeByMajorEvent @@ -19,7 +19,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class NoticeByMajorViewModel @Inject constructor( - private val appPreference: AppPreferences, + private val appPreference: AppPreferenceRepository, private val fetchNoticesPerPage: FetchNoticesPerPage, private val submitNotificationPreferences: SubmitNotificationPreferences ): BaseViewModel() { diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt index dcd71b18..1757f282 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt @@ -6,10 +6,9 @@ import android.content.Context import android.content.pm.PackageManager import android.util.Log import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.model.di.ApplicationContext import com.doyoonkim.domain.usecases.FetchTopicSubscriptionStatus import com.doyoonkim.domain.usecases.SubmitNotificationPreferences @@ -29,7 +28,7 @@ class NotificationPreferencesViewModel @Inject constructor( private val submitNotificationPreferences: SubmitNotificationPreferences, private val fetchTopicSubscriptionStatus: FetchTopicSubscriptionStatus, private val notificationManager: NotificationManager, - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, @ApplicationContext private val context: Context ) : BaseViewModel() { private val TAG = this.javaClass.name diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt index 25fc2173..70cf48c0 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt @@ -1,10 +1,8 @@ package com.doyoonkim.main.viewmodel -import android.util.Log -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.SyncDataWithUpdateDatabase import com.doyoonkim.main.contract.SettingsEvent import com.doyoonkim.main.contract.SettingsMutation @@ -15,7 +13,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class SettingsViewModel @Inject constructor( - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase ) : BaseViewModel() { private val TAG = this.javaClass.name diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt index 4c9f6abe..12c34ff4 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt @@ -4,9 +4,8 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences -import com.doyoonkim.common.di.ApplicationScope import com.doyoonkim.common.di.TokenHandler +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.abtest.FirebaseRemoteConfigRepository import com.doyoonkim.domain.usecases.SyncDataWithUpdateDatabase import com.doyoonkim.main.contract.SplashEvent @@ -21,7 +20,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class SplashViewModel @Inject constructor( - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase, private val tokenHandler: TokenHandler, private val remoteConfigRepository: FirebaseRemoteConfigRepository, @@ -83,17 +82,12 @@ class SplashViewModel @Inject constructor( private fun preProcessCompletionCheck() { with(uiState.value) { - if ( - syncStatus != SyncStatus.REQUESTED - ) { - if ( - syncStatus == SyncStatus.COMPLETED - ) { - sendSideEffect(SplashSideEffect.Dismiss) - } else { - sendSideEffect(SplashSideEffect.DismissWithError) - } + if (syncStatus == SyncStatus.COMPLETED) { + sendSideEffect(SplashSideEffect.Dismiss) + return } + + sendSideEffect(SplashSideEffect.DismissWithError) } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt index 0f5951c2..4aeebb50 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.common.di.AppPreferences +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.AsyncNoticeWidgetTaskScheduler import com.doyoonkim.main.contract.WidgetConfigEvent import com.doyoonkim.main.contract.WidgetConfigMutation @@ -20,7 +20,7 @@ import javax.inject.Inject * Created 2/16/26 at 8:38 PM */ class WidgetConfigViewModel @Inject constructor( - private val appPreference: AppPreferences, + private val appPreference: AppPreferenceRepository, private val noticeWidgetTaskScheduler: AsyncNoticeWidgetTaskScheduler, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : BaseViewModel() { diff --git a/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt b/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt index 5b46ec18..8399c8af 100644 --- a/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt +++ b/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt @@ -2,31 +2,24 @@ package com.doyoonkim.widget.worker import android.content.Context import android.util.Log -import androidx.glance.appwidget.GlanceAppWidgetManager -import androidx.glance.appwidget.state.updateAppWidgetState -import androidx.glance.appwidget.updateAll import androidx.work.CoroutineWorker import androidx.work.ListenableWorker import androidx.work.WorkerParameters -import com.doyoonkim.common.di.AppPreferences import com.doyoonkim.model.di.ApplicationContext import com.doyoonkim.common.worker.IntermediateWorkerFactory +import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.interfaces.NoticeRemoteRepository import com.doyoonkim.model.NoticeVO import com.doyoonkim.model.WidgetCategoryPolicy -import com.doyoonkim.widget.model.WidgetKey import com.doyoonkim.widget.model.WidgetNoticeVO import com.doyoonkim.widget.model.WidgetState -import com.doyoonkim.widget.notices.KnuticeWidget import com.doyoonkim.widget.util.WidgetStateUpdater -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json import javax.inject.Inject class KnuticeWidgetSync( appContext: Context, workerParam: WorkerParameters, - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : CoroutineWorker(appContext, workerParam) { @@ -94,7 +87,7 @@ class KnuticeWidgetSync( // Factory class Factory @Inject constructor( @ApplicationContext private val context: Context, - private val appPreferences: AppPreferences, + private val appPreferences: AppPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : IntermediateWorkerFactory { From 62c5e5fdfd8c34a279a205a3547851bb53cebf3f Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 03:51:44 -0400 Subject: [PATCH 05/11] KAN-111 [CHORE] Modify Local App Preference Repository Structure - Add necessary library dependency (Kotlin Serialization) --- core/data/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index e593cce6..35067c4c 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -4,6 +4,9 @@ plugins { id("knutice.android.library") id("knutice.android.dagger") id("knutice.android.room") + + // Kotlin Serialization + alias(libs.plugins.kotlinSerialization) } configure() { @@ -19,4 +22,7 @@ dependencies { implementation(projects.core.network) implementation(projects.core.domain) // Dependency Inversion implementation(projects.common) + + // Kotlin Serialization + implementation(libs.kotlin.serialization) } \ No newline at end of file From 356e6750ffdf5d92bbe13141a23416871c66796a Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 25 Mar 2026 04:07:52 -0400 Subject: [PATCH 06/11] KAN-111 [REFACTOR] Minor Fix based on Gemini Review - Update TAG variables to match with its class name. - Change the filename to ensure name consistency. --- .../data/repository/remote/RemoteContentRepositoryImpl.kt | 2 +- ...referenceRepository.kt => RemotePreferenceRepositoryImpl.kt} | 2 +- .../{RemoteTokenRepository.kt => RemoteTokenRepositoryImpl.kt} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename core/data/src/main/java/com/doyoonkim/data/repository/remote/{RemotePreferenceRepository.kt => RemotePreferenceRepositoryImpl.kt} (98%) rename core/data/src/main/java/com/doyoonkim/data/repository/remote/{RemoteTokenRepository.kt => RemoteTokenRepositoryImpl.kt} (98%) diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt index 4cc20ba1..282be0e9 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt @@ -15,7 +15,7 @@ class RemoteContentRepositoryImpl @Inject constructor( ) : NoticeRemoteRepository, TipRemoteRepository { - private val TAG = "RemoteRepositoryImpl" + private val TAG = "RemoteContentRepositoryImpl" override suspend fun queryTopThreeNotices(category: String): List? { remoteSource.getNoticesPerPage(category = category, size = 3).fold( diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt similarity index 98% rename from core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt index e7bd1021..7eaf7d0b 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepository.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt @@ -19,7 +19,7 @@ class RemotePreferenceRepositoryImpl @Inject constructor( private val appPreference: AppPreferenceRepository ) : TopicSubscriptionRemoteRepository, UserReportRemoteRepository { - private val TAG = "RemotePreferenceRepository" + private val TAG = "RemotePreferenceRepositoryImpl" override fun queryTopicSubscriptionStatus(topicType: TopicType) = flow { remoteSource.getTopicSubscriptionStatus( diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt similarity index 98% rename from core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt index 09151568..f32ce3b6 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepository.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt @@ -19,7 +19,7 @@ class RemoteTokenRepositoryImpl @Inject constructor( private val appPreference: AppPreferenceRepository ) : TokenRemoteRepository { - private val TAG = "RemoteTokenRepository" + private val TAG = "RemoteTokenRepositoryImpl" override suspend fun requestUpdateFcmToken(body: TokenUpdateBody): TokenStatus { remoteSource.updateDeviceToken( From 71e2c0dfe37074f23dbec94bf5c55de40c335894 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Thu, 26 Mar 2026 00:03:04 -0400 Subject: [PATCH 07/11] KAN-111 [REFACTOR] Architectural Improvement on AppPreferenceRepository - Made architectural improvement on AppPreferenceRepository to prevent potential technical debt caused by monolithic Repository implementation. * Decentralize Monolithic AppPreferenceRepository to achieve strict Separation of Concern via Interface Segregation. * Necessary Binding functions have been defined for Dependency Injection via Dagger 2. --- .../com/doyoonkim/data/di/LocalModules.kt | 34 ++++++- .../AppDatabasePreferenceRepositoryImpl.kt | 42 +++++++++ .../local/AppPreferenceRepositoryImpl.kt | 94 ------------------- ...AppSubscriptionPreferenceRepositoryImpl.kt | 30 ++++++ .../local/AppTokenPreferenceRepositoryImpl.kt | 30 ++++++ .../AppWidgetPreferenceRepositoryImpl.kt | 44 +++++++++ ....kt => AppDatabasePreferenceRepository.kt} | 14 +-- .../AppSubscriptionPreferenceRepository.kt | 13 +++ .../AppTokenPreferenceRepository.kt | 13 +++ .../AppWidgetPreferenceRepository.kt | 15 +++ 10 files changed, 217 insertions(+), 112 deletions(-) create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/local/AppDatabasePreferenceRepositoryImpl.kt delete mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/local/AppSubscriptionPreferenceRepositoryImpl.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/local/AppTokenPreferenceRepositoryImpl.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/local/AppWidgetPreferenceRepositoryImpl.kt rename core/domain/src/main/java/com/doyoonkim/domain/interfaces/{AppPreferenceRepository.kt => AppDatabasePreferenceRepository.kt} (55%) create mode 100644 core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppSubscriptionPreferenceRepository.kt create mode 100644 core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppTokenPreferenceRepository.kt create mode 100644 core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppWidgetPreferenceRepository.kt diff --git a/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt b/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt index 0d51a628..2e65214d 100644 --- a/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt +++ b/core/data/src/main/java/com/doyoonkim/data/di/LocalModules.kt @@ -1,12 +1,18 @@ package com.doyoonkim.data.di import android.content.Context -import com.doyoonkim.data.repository.local.AppPreferenceRepositoryImpl +import com.doyoonkim.data.repository.local.AppDatabasePreferenceRepositoryImpl +import com.doyoonkim.data.repository.local.AppSubscriptionPreferenceRepositoryImpl +import com.doyoonkim.data.repository.local.AppTokenPreferenceRepositoryImpl +import com.doyoonkim.data.repository.local.AppWidgetPreferenceRepositoryImpl import com.doyoonkim.model.di.ApplicationContext import com.doyoonkim.data.repository.local.LocalRepositoryImpl import com.doyoonkim.data.repository.local.LocalWidgetCacheRepositoryImpl import com.doyoonkim.data.room.LocalDatabase -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.BookmarkLocalRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.interfaces.NoticeLocalRepository @@ -54,7 +60,25 @@ object RoomDatabaseModule { abstract class LocalPreferenceModule { @Binds @Singleton - abstract fun bindLocalAppPreferenceRepository( - impl: AppPreferenceRepositoryImpl - ): AppPreferenceRepository + abstract fun bindLocalAppDatabasePreferenceRepository( + impl: AppDatabasePreferenceRepositoryImpl + ): AppDatabasePreferenceRepository + + @Binds + @Singleton + abstract fun bindLocalAppTokenPreferenceRepository( + impl: AppTokenPreferenceRepositoryImpl + ): AppTokenPreferenceRepository + + @Binds + @Singleton + abstract fun bindLocalAppSubscriptionPreferenceRepository( + impl: AppSubscriptionPreferenceRepositoryImpl + ): AppSubscriptionPreferenceRepository + + @Binds + @Singleton + abstract fun bindLocalAppWidgetPreferenceRepository( + impl: AppWidgetPreferenceRepositoryImpl + ): AppWidgetPreferenceRepository } \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppDatabasePreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppDatabasePreferenceRepositoryImpl.kt new file mode 100644 index 00000000..4a3b8c22 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppDatabasePreferenceRepositoryImpl.kt @@ -0,0 +1,42 @@ +package com.doyoonkim.data.repository.local + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository +import javax.inject.Inject + +class AppDatabasePreferenceRepositoryImpl @Inject constructor( + private val appPref: SharedPreferences +) : AppDatabasePreferenceRepository { + + companion object { + // Database Sync + private const val DB_SYNC_PARTIAL_FAIL = "DB_SYNC_PARTIAL_FAIL" + + private const val DB_SYNC_1_2_STATUS = "DB_SYNC_1_2_STATUS" + private const val DB_SYNC_2_3_STATUS = "DB_SYNC_2_3_STATUS" + } + + + override fun isDatabaseSyncCompleted(): Boolean { + // If sync 2_3 is required, return + // appPref.getBoolean(DB_SYNC_1_2_STATUS, false) && appPref.getBoolean(DB_SYNC_2_3_STATUS, false) + + return with(appPref) { + getBoolean(DB_SYNC_1_2_STATUS, false) + && getBoolean(DB_SYNC_2_3_STATUS, false) + } + } + + override fun isPartialFailedDuringDatabaseSync() = appPref.getBoolean(DB_SYNC_PARTIAL_FAIL, false) + + override fun setSyncStatus_1_2(status: Boolean) = + appPref.edit { putBoolean(DB_SYNC_1_2_STATUS, status) } + + override fun setSyncStatus_2_3(status: Boolean) = + appPref.edit { putBoolean(DB_SYNC_2_3_STATUS, status) } + + override fun setDatabaseSyncPartialFailedStatus(status: Boolean) = + appPref.edit { putBoolean(DB_SYNC_PARTIAL_FAIL, status) } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt deleted file mode 100644 index 1fea1686..00000000 --- a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppPreferenceRepositoryImpl.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.doyoonkim.data.repository.local - -import android.content.SharedPreferences -import androidx.core.content.edit -import com.doyoonkim.domain.interfaces.AppPreferenceRepository -import com.doyoonkim.model.WidgetCategoryPolicy -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import javax.inject.Inject - -class AppPreferenceRepositoryImpl @Inject constructor( - private val appPref: SharedPreferences -) : AppPreferenceRepository { - private val DB_SYNC_STATUS = "DB_SYNC_STATUS" - private val DB_SYNC_PARTIAL_FAIL = "DB_SYNC_PARTIAL_FAIL" - - private val DB_SYNC_1_2_STATUS = "DB_SYNC_1_2_STATUS" - private val DB_SYNC_2_3_STATUS = "DB_SYNC_2_3_STATUS" - - // Token Caching - private val DEVICE_TOKEN = "DEVICE_TOKEN" - - // Major Subscription Status - private val SUBSCRIBED_MAJOR = "SUBSCRIBED_MAJOR" - - // Widget Configuration (Category Selection) - private val WIDGET_CATEGORY = "WIDGET_CATEGORY" - - private val json = Json { - encodeDefaults = true - ignoreUnknownKeys = true - classDiscriminator = "type" - } - - /** - * updateDeviceToken - * @param token: Unique FCM token issued to this device. - */ - override fun updateDeviceToken(token: String) { - appPref.edit { putString(DEVICE_TOKEN, token) } - } - - override fun getCachedToken(): String? { - return appPref.getString(DEVICE_TOKEN, null) - } - - /** - * Major Subscription Status - */ - override fun getSubscribedMajor(): String? { - return appPref.getString(SUBSCRIBED_MAJOR, null) - } - - override fun updateSubscribedMajor(newMajor: String) { - appPref.edit { putString(SUBSCRIBED_MAJOR, newMajor) } - } - - /** - * Widget Configuration - */ - override fun getWidgetCategoryPolicy(): WidgetCategoryPolicy { - val cachedPolicy = appPref.getString(WIDGET_CATEGORY, null) - ?: return WidgetCategoryPolicy.Unconfigured - - return json.decodeFromString(cachedPolicy) - } - - override fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) { - val serializedString = json.encodeToString(policy) - appPref.edit { putString(WIDGET_CATEGORY, serializedString) } - } - - override fun isDatabaseSyncCompleted(): Boolean { - // If sync 2_3 is required, return - // appPref.getBoolean(DB_SYNC_1_2_STATUS, false) && appPref.getBoolean(DB_SYNC_2_3_STATUS, false) - - return with(appPref) { - getBoolean(DB_SYNC_1_2_STATUS, false) - && getBoolean(DB_SYNC_2_3_STATUS, false) - } - } - - override fun isPartialFailedDuringDatabaseSync() = appPref.getBoolean(DB_SYNC_PARTIAL_FAIL, false) - - override fun setSyncStatus_1_2(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_1_2_STATUS, status) } - - override fun setSyncStatus_2_3(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_2_3_STATUS, status) } - - override fun setDatabaseSyncPartialFailedStatus(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_PARTIAL_FAIL, status) } - -} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppSubscriptionPreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppSubscriptionPreferenceRepositoryImpl.kt new file mode 100644 index 00000000..4c458245 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppSubscriptionPreferenceRepositoryImpl.kt @@ -0,0 +1,30 @@ +package com.doyoonkim.data.repository.local + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import javax.inject.Inject + +class AppSubscriptionPreferenceRepositoryImpl @Inject constructor( + private val appPref: SharedPreferences +) : AppSubscriptionPreferenceRepository { + + companion object { + private const val TAG = "AppSubscriptionPreferenceRepositoryImpl" + + // Major Subscription Status + private const val SUBSCRIBED_MAJOR = "SUBSCRIBED_MAJOR" + } + + /** + * Major Subscription Status + */ + override fun getSubscribedMajor(): String? { + return appPref.getString(SUBSCRIBED_MAJOR, null) + } + + override fun updateSubscribedMajor(newMajor: String) { + appPref.edit { putString(SUBSCRIBED_MAJOR, newMajor) } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppTokenPreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppTokenPreferenceRepositoryImpl.kt new file mode 100644 index 00000000..d92cb3cd --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppTokenPreferenceRepositoryImpl.kt @@ -0,0 +1,30 @@ +package com.doyoonkim.data.repository.local + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +import javax.inject.Inject + +class AppTokenPreferenceRepositoryImpl @Inject constructor( + private val appPref: SharedPreferences +) : AppTokenPreferenceRepository { + + companion object { + private const val TAG = "AppTokenPreferenceRepositoryImpl" + // Token Caching + private const val DEVICE_TOKEN = "DEVICE_TOKEN" + } + + /** + * updateDeviceToken + * @param token: Unique FCM token issued to this device. + */ + override fun updateDeviceToken(token: String) { + appPref.edit { putString(DEVICE_TOKEN, token) } + } + + override fun getCachedToken(): String? { + return appPref.getString(DEVICE_TOKEN, null) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/AppWidgetPreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppWidgetPreferenceRepositoryImpl.kt new file mode 100644 index 00000000..c9faffa3 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/AppWidgetPreferenceRepositoryImpl.kt @@ -0,0 +1,44 @@ +package com.doyoonkim.data.repository.local + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository +import com.doyoonkim.model.WidgetCategoryPolicy +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import javax.inject.Inject + +class AppWidgetPreferenceRepositoryImpl @Inject constructor( + private val appPref: SharedPreferences +) : AppWidgetPreferenceRepository { + + companion object { + private const val TAG = "AppWidgetPreferenceRepositoryImpl" + + // Widget Configuration (Category Selection) + private const val WIDGET_CATEGORY = "WIDGET_CATEGORY" + + private val json = Json { + encodeDefaults = true + ignoreUnknownKeys = true + classDiscriminator = "type" + } + + } + + /** + * Widget Configuration + */ + override fun getWidgetCategoryPolicy(): WidgetCategoryPolicy { + val cachedPolicy = appPref.getString(WIDGET_CATEGORY, null) + ?: return WidgetCategoryPolicy.Unconfigured + + return json.decodeFromString(cachedPolicy) + } + + override fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) { + val serializedString = json.encodeToString(policy) + appPref.edit { putString(WIDGET_CATEGORY, serializedString) } + } + +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt similarity index 55% rename from core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt rename to core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt index 457f6bc8..582b613b 100644 --- a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppPreferenceRepository.kt +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt @@ -6,19 +6,7 @@ import com.doyoonkim.model.WidgetCategoryPolicy * @author kimdoyoon * Created 3/25/26 at 1:42 AM */ -interface AppPreferenceRepository { - - fun updateDeviceToken(token: String) - - fun getCachedToken(): String? - - fun getSubscribedMajor(): String? - - fun updateSubscribedMajor(newMajor: String) - - fun getWidgetCategoryPolicy(): WidgetCategoryPolicy - - fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) +interface AppDatabasePreferenceRepository { fun isDatabaseSyncCompleted(): Boolean diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppSubscriptionPreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppSubscriptionPreferenceRepository.kt new file mode 100644 index 00000000..5c036de5 --- /dev/null +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppSubscriptionPreferenceRepository.kt @@ -0,0 +1,13 @@ +package com.doyoonkim.domain.interfaces + +/** + * @author kimdoyoon + * Created 3/25/26 at 10:40 PM + */ +interface AppSubscriptionPreferenceRepository { + + fun getSubscribedMajor(): String? + + fun updateSubscribedMajor(newMajor: String) + +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppTokenPreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppTokenPreferenceRepository.kt new file mode 100644 index 00000000..c9465382 --- /dev/null +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppTokenPreferenceRepository.kt @@ -0,0 +1,13 @@ +package com.doyoonkim.domain.interfaces + +/** + * @author kimdoyoon + * Created 3/25/26 at 10:40 PM + */ +interface AppTokenPreferenceRepository { + + fun updateDeviceToken(token: String) + + fun getCachedToken(): String? + +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppWidgetPreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppWidgetPreferenceRepository.kt new file mode 100644 index 00000000..a7785f3c --- /dev/null +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppWidgetPreferenceRepository.kt @@ -0,0 +1,15 @@ +package com.doyoonkim.domain.interfaces + +import com.doyoonkim.model.WidgetCategoryPolicy + +/** + * @author kimdoyoon + * Created 3/25/26 at 10:40 PM + */ +interface AppWidgetPreferenceRepository { + + fun getWidgetCategoryPolicy(): WidgetCategoryPolicy + + fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) + +} \ No newline at end of file From 0084d01872045dbe114faaa9ef8054382771c12a Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Thu, 26 Mar 2026 00:04:05 -0400 Subject: [PATCH 08/11] KAN-111 [REFACTOR] Architectural Improvement on AppPreferenceRepository - Made architectural improvement on AppPreferenceRepository to prevent potential technical debt caused by monolithic Repository implementation. * Update Dagger Components to provide necessary AppPreferences' dependencies. --- .../com/doyoonkim/knutice/SplashActivity.kt | 2 +- .../knutice/WidgetConfigurationActivity.kt | 2 +- .../doyoonkim/knutice/WidgetSyncObserver.kt | 10 +++-- .../knutice/di/components/AppComponent.kt | 2 + .../NotificationServiceComponent.kt | 6 +-- .../knutice/di/components/SceneComponents.kt | 37 +++++++++++-------- .../knutice/di/components/WidgetComponent.kt | 6 +-- .../knutice/di/util/DefaultSystemService.kt | 13 ++++++- .../knutice/navigation/MainServiceNavGraph.kt | 9 +++-- .../navigation/bookmarkServiceGraph.kt | 2 +- .../knutice/navigation/campusServiceGraph.kt | 2 +- 11 files changed, 55 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt b/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt index 2cd48d13..4fb9cce6 100644 --- a/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt @@ -49,7 +49,7 @@ class SplashActivity : ComponentActivity() { systemServices = appComponent, networkProvider = appComponent, localStorageProvider = appComponent, - localCacheProvider = appComponent, + localPreferenceProvider = appComponent, firebaseInfrastructureProvider = appComponent ) diff --git a/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt b/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt index 3faabf9a..13287697 100644 --- a/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/WidgetConfigurationActivity.kt @@ -41,7 +41,7 @@ class WidgetConfigurationActivity : ComponentActivity() { val appComponent = (application as MainApplication).appComponent val sceneComponent = DaggerWidgetConfigSceneComponent.factory().create( systemService = appComponent, - localCacheProvider = appComponent + localPreferenceProvider = appComponent ) WidgetPreferencesScreen( diff --git a/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt b/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt index 4e593178..a6531f00 100644 --- a/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt +++ b/app/src/main/java/com/doyoonkim/knutice/WidgetSyncObserver.kt @@ -3,7 +3,8 @@ package com.doyoonkim.knutice import android.util.Log import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.model.WidgetCategoryPolicy import com.doyoonkim.widget.model.CarrelWidgetState @@ -17,7 +18,8 @@ import javax.inject.Inject class WidgetSyncObserver @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val widgetStateUpdater: WidgetStateUpdater, - private val appPreference: AppPreferenceRepository, + private val appWidgetPreference: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val applicationScope: CoroutineScope ): DefaultLifecycleObserver { @@ -42,7 +44,7 @@ class WidgetSyncObserver @Inject constructor( contentUrl = vo.url ) } - val categoryPolicy = appPreference.getWidgetCategoryPolicy() + val categoryPolicy = appWidgetPreference.getWidgetCategoryPolicy() // Category Mismatch: Recent Category Changed via WidgetConfiguration. Widget is already updated. if (categoryPolicy != localWidgetCacheRepository.widgetCategoryCacheState.value) return @@ -56,7 +58,7 @@ class WidgetSyncObserver @Inject constructor( ) } is WidgetCategoryPolicy.Major -> { - val majorCategory = appPreference.getSubscribedMajor() + val majorCategory = appSubscriptionPreference.getSubscribedMajor() majorCategory?.let { updateNoticeWidgetState( WidgetState(it, noticeCache) diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt index 1bcaefc0..9ec72881 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/AppComponent.kt @@ -22,6 +22,7 @@ import com.doyoonkim.infrastructure.di.FirebaseAnalyticsModule import com.doyoonkim.infrastructure.di.FirebaseRemoteConfigModule import com.doyoonkim.knutice.di.util.FirebaseInfrastructureProvider import com.doyoonkim.knutice.di.util.LocalCacheProvider +import com.doyoonkim.knutice.di.util.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -51,6 +52,7 @@ interface AppComponent : NetworkProvider, LocalStorageProvider, LocalCacheProvider, + LocalPreferenceProvider, FirebaseInfrastructureProvider { fun inject(app: MainApplication) diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt index f67cb86a..91d76d00 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/NotificationServiceComponent.kt @@ -5,7 +5,7 @@ import com.doyoonkim.data.di.ImageRemoteModule import com.doyoonkim.data.di.TokenRemoteModule import com.doyoonkim.domain.di.TokenUseCaseModule import com.doyoonkim.knutice.di.modules.WorkSchedulerModule -import com.doyoonkim.knutice.di.util.LocalCacheProvider +import com.doyoonkim.knutice.di.util.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices import com.doyoonkim.notification.di.NotificationModule @@ -15,7 +15,7 @@ import dagger.Component @Component( dependencies = [ SystemServices::class, - LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -35,7 +35,7 @@ interface NotificationServiceComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localCacheProvider: LocalCacheProvider + localPreferenceProvider: LocalPreferenceProvider ): NotificationServiceComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt index e08ec123..f1f1548f 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/SceneComponents.kt @@ -17,6 +17,7 @@ import com.doyoonkim.knutice.di.modules.ViewModelFactoryModule import com.doyoonkim.knutice.di.modules.WorkSchedulerModule import com.doyoonkim.knutice.di.util.FirebaseInfrastructureProvider import com.doyoonkim.knutice.di.util.LocalCacheProvider +import com.doyoonkim.knutice.di.util.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -39,6 +40,7 @@ import javax.inject.Singleton dependencies = [ SystemServices::class, LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class, FirebaseInfrastructureProvider::class ], @@ -60,6 +62,7 @@ interface HomeSceneComponent { systemServices: SystemServices, networkProvider: NetworkProvider, localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): HomeSceneComponent } @@ -69,7 +72,7 @@ interface HomeSceneComponent { dependencies = [ SystemServices::class, NetworkProvider::class, - LocalCacheProvider::class + LocalPreferenceProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -88,7 +91,7 @@ interface NoticeByMajorSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localCacheProvider: LocalCacheProvider + localPreferenceProvider: LocalPreferenceProvider ): NoticeByMajorSceneComponent } } @@ -98,7 +101,7 @@ interface NoticeByMajorSceneComponent { dependencies = [ SystemServices::class, LocalStorageProvider::class, - LocalCacheProvider::class + LocalPreferenceProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -113,7 +116,7 @@ interface BookmarkListSceneComponent { interface Factory { fun create( systemServices: SystemServices, - localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, localStorageProvider: LocalStorageProvider ): BookmarkListSceneComponent } @@ -225,7 +228,7 @@ interface NoticeInCategorySceneComponent { @Component( dependencies = [ SystemServices::class, - LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -242,7 +245,7 @@ interface CustomerServiceSceneComponent { interface Factory { fun create( systemService: SystemServices, - localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, networkProvider: NetworkProvider ): CustomerServiceSceneComponent } @@ -252,7 +255,7 @@ interface CustomerServiceSceneComponent { @Component( dependencies = [ SystemServices::class, - LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -269,7 +272,7 @@ interface NotificationPreferencesSceneComponent { interface Factory { fun create( systemServices: SystemServices, - localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, networkProvider: NetworkProvider ): NotificationPreferencesSceneComponent } @@ -281,7 +284,7 @@ interface NotificationPreferencesSceneComponent { SystemServices::class, NetworkProvider::class, LocalStorageProvider::class, - LocalCacheProvider::class + LocalPreferenceProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -299,7 +302,7 @@ interface SettingsSceneComponent { systemServices: SystemServices, networkProvider: NetworkProvider, localStorageProvider: LocalStorageProvider, - localCacheProvider: LocalCacheProvider + localPreferenceProvider: LocalPreferenceProvider ): SettingsSceneComponent } } @@ -310,7 +313,7 @@ interface SettingsSceneComponent { SystemServices::class, NetworkProvider::class, LocalStorageProvider::class, - LocalCacheProvider::class, + LocalPreferenceProvider::class, FirebaseInfrastructureProvider::class ], modules = [ @@ -331,14 +334,14 @@ interface SplashSceneComponent { systemServices: SystemServices, networkProvider: NetworkProvider, localStorageProvider: LocalStorageProvider, - localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): SplashSceneComponent } } @Component( - dependencies = [SystemServices::class, LocalCacheProvider::class], + dependencies = [SystemServices::class, LocalPreferenceProvider::class], modules = [ ViewModelFactoryModule::class, WidgetConfigSceneModule::class, @@ -353,7 +356,7 @@ interface WidgetConfigSceneComponent { interface Factory { fun create( systemService: SystemServices, - localCacheProvider: LocalCacheProvider + localPreferenceProvider: LocalPreferenceProvider ): WidgetConfigSceneComponent } } @@ -362,7 +365,8 @@ interface WidgetConfigSceneComponent { dependencies = [ SystemServices::class, NetworkProvider::class, - LocalCacheProvider::class + LocalCacheProvider::class, + LocalPreferenceProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -378,7 +382,8 @@ interface CarrelStatusSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localCacheProvider: LocalCacheProvider + localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider ): CarrelStatusSceneComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt b/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt index 75d43f3d..b8f790ce 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/components/WidgetComponent.kt @@ -1,7 +1,7 @@ package com.doyoonkim.knutice.di.components import com.doyoonkim.knutice.di.modules.WorkSchedulerModule -import com.doyoonkim.knutice.di.util.LocalCacheProvider +import com.doyoonkim.knutice.di.util.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices import com.doyoonkim.widget.di.WidgetDependency @@ -10,7 +10,7 @@ import dagger.Component @Component( dependencies = [ SystemServices::class, - LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -24,7 +24,7 @@ interface WidgetComponent: WidgetDependency { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localCacheProvider: LocalCacheProvider + localPreferenceProvider: LocalPreferenceProvider ): WidgetComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt b/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt index 35a93e52..1afc3f4c 100644 --- a/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt +++ b/app/src/main/java/com/doyoonkim/knutice/di/util/DefaultSystemService.kt @@ -6,7 +6,10 @@ import android.content.Context import android.content.SharedPreferences import androidx.work.WorkManager import com.doyoonkim.common.analytics.AnalyticsLogger -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.BookmarkLocalRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.interfaces.NoticeLocalRepository @@ -48,10 +51,16 @@ interface LocalStorageProvider { } interface LocalCacheProvider { - fun localAppPreferenceRepository(): AppPreferenceRepository fun localWidgetCacheRepository(): LocalWidgetCacheRepository } +interface LocalPreferenceProvider { + fun localAppDatabasePreferenceRepository(): AppDatabasePreferenceRepository + fun localAppSubscriptionPreferenceRepository(): AppSubscriptionPreferenceRepository + fun localAppTokenPreferenceRepository(): AppTokenPreferenceRepository + fun localAppWidgetPreferenceRepository(): AppWidgetPreferenceRepository +} + interface FirebaseInfrastructureProvider { fun analyticsLogger(): AnalyticsLogger fun remoteConfigRepository(): FirebaseRemoteConfigRepository diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt index ee3888ec..fb949048 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt @@ -63,6 +63,7 @@ fun NavGraphBuilder.mainServiceNavGraph( systemServices = appComponent, networkProvider = appComponent, localCacheProvider = appComponent, + localPreferenceProvider = appComponent, firebaseInfrastructureProvider = appComponent ) } @@ -108,7 +109,7 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerNoticeByMajorSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localCacheProvider = appComponent + localPreferenceProvider = appComponent ) } @@ -336,7 +337,7 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerSettingsSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localCacheProvider = appComponent, + localPreferenceProvider = appComponent, localStorageProvider = appComponent ) } @@ -375,7 +376,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerNotificationPreferencesSceneComponent.factory().create( systemServices = appComponent, - localCacheProvider = appComponent, + localPreferenceProvider = appComponent, networkProvider = appComponent ) } @@ -405,7 +406,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerCustomerServiceSceneComponent.factory().create( systemService = appComponent, - localCacheProvider = appComponent, + localPreferenceProvider = appComponent, networkProvider = appComponent ) } diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt index 987421b7..3edd1b68 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/bookmarkServiceGraph.kt @@ -36,7 +36,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( val sceneComponent = remember(appComponent) { DaggerBookmarkListSceneComponent.factory().create( systemServices = appComponent, - localCacheProvider = appComponent, + localPreferenceProvider = appComponent, localStorageProvider = appComponent ) } diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt index 9f7cf097..5d6af441 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt @@ -44,7 +44,7 @@ fun NavGraphBuilder.campusServiceGraph( val sceneComponent = remember(appComponent) { DaggerCarrelStatusSceneComponent.factory().create( - appComponent, appComponent, appComponent + appComponent, appComponent, appComponent, appComponent ) } From fcd66c7cecc5d0f546f6307aa93c821f30ecb8da Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Thu, 26 Mar 2026 00:05:19 -0400 Subject: [PATCH 09/11] KAN-111 [REFACTOR] Architectural Improvement on AppPreferenceRepository - Made architectural improvement on AppPreferenceRepository to prevent potential technical debt caused by monolithic Repository implementation. * Replace legacy monolithic AppPreferenceRepository dependency to updated dependencies based on the context and operations. --- .../local/LocalWidgetCacheRepositoryImpl.kt | 6 +++--- .../doyoonkim/notification/fcm/TokenHandler.kt | 7 +++++-- .../bookmark/viewmodel/BookmarkListViewModel.kt | 6 +++--- .../main/viewmodel/CarrelStatusViewModel.kt | 6 +++--- .../main/viewmodel/CustomerServiceViewModel.kt | 2 -- .../doyoonkim/main/viewmodel/HomeViewModel.kt | 10 ++++++---- .../main/viewmodel/NoticeByMajorViewModel.kt | 10 +++++----- .../NotificationPreferencesViewModel.kt | 10 +++++----- .../main/viewmodel/SettingsViewModel.kt | 8 ++++---- .../doyoonkim/main/viewmodel/SplashViewModel.kt | 14 ++++++-------- .../main/viewmodel/WidgetConfigViewModel.kt | 12 +++++++----- .../doyoonkim/widget/worker/KnuticeWidgetSync.kt | 16 ++++++++++------ 12 files changed, 57 insertions(+), 50 deletions(-) diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt index ac85bab8..28f42e09 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/local/LocalWidgetCacheRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.doyoonkim.data.repository.local import android.util.Log -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.model.CarrelRoomStatusVO import com.doyoonkim.model.NoticeVO @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.update import javax.inject.Inject class LocalWidgetCacheRepositoryImpl @Inject constructor( - appPreference: AppPreferenceRepository + appWidgetPreference: AppWidgetPreferenceRepository ) : LocalWidgetCacheRepository { companion object { @@ -22,7 +22,7 @@ class LocalWidgetCacheRepositoryImpl @Inject constructor( private val _noticeCacheState = MutableStateFlow>(emptyList()) override val noticeCacheState = _noticeCacheState.asStateFlow() - private val _widgetCategoryCacheState = MutableStateFlow(appPreference.getWidgetCategoryPolicy()) + private val _widgetCategoryCacheState = MutableStateFlow(appWidgetPreference.getWidgetCategoryPolicy()) override val widgetCategoryCacheState = _widgetCategoryCacheState.asStateFlow() private val _carrelCacheState = MutableStateFlow>(emptyList()) diff --git a/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt b/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt index 7e1c7fcf..38ce4559 100644 --- a/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt +++ b/core/notification/src/main/java/com/doyoonkim/notification/fcm/TokenHandler.kt @@ -2,7 +2,7 @@ package com.doyoonkim.notification.fcm import android.util.Log import com.doyoonkim.common.di.TokenHandler -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository import com.doyoonkim.domain.usecases.ValidateDeviceToken import com.doyoonkim.model.TokenStatus import com.doyoonkim.model.requestBody.DeviceTokenBody @@ -14,7 +14,7 @@ import javax.inject.Inject class TokenHandlerImpl @Inject constructor( private val validateDeviceToken: ValidateDeviceToken, - private val appPreferences: AppPreferenceRepository + private val appPreferences: AppTokenPreferenceRepository ) : TokenHandler { private val TAG = this.javaClass.name @@ -52,6 +52,9 @@ class TokenHandlerImpl @Inject constructor( val token = Firebase.messaging.token.await() val cached = appPreferences.getCachedToken() + Log.d(TAG, "Received: $token") + Log.d(TAG, "Cached: $cached") + return token == cached } } \ No newline at end of file diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt index 8ab20dca..98923fab 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/viewmodel/BookmarkListViewModel.kt @@ -9,7 +9,7 @@ import com.doyoonkim.bookmark.contract.BookmarkListSideEffect import com.doyoonkim.bookmark.contract.BookmarkListState import com.doyoonkim.common.base.BaseViewModel import com.doyoonkim.common.navigation.BookmarkInfo -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository import com.doyoonkim.domain.usecases.FetchAllBookmarks import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @@ -17,7 +17,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class BookmarkListViewModel @Inject constructor( - private val appPreferences: AppPreferenceRepository, + private val appDatabasePreference: AppDatabasePreferenceRepository, private val fetchAllBookmarks: FetchAllBookmarks ) : BaseViewModel() { @@ -35,7 +35,7 @@ class BookmarkListViewModel @Inject constructor( override fun handleEvent(event: BookmarkListEvent) { when (event) { is BookmarkListEvent.CheckSyncStatus -> { - if (appPreferences.isPartialFailedDuringDatabaseSync()) { + if (appDatabasePreference.isPartialFailedDuringDatabaseSync()) { mutate(BookmarkListMutation.SyncNeeded) } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt index 4440bbae..eb3144f4 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CarrelStatusViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import kotlinx.coroutines.flow.MutableStateFlow @@ -15,7 +15,7 @@ import javax.inject.Inject class CarrelStatusViewModel @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val carrelStatusRepository: CarrelStatusRemoteRepository, - private val appPreference: AppPreferenceRepository + private val appTokenPreference: AppTokenPreferenceRepository ) : ViewModel() { // States @@ -26,7 +26,7 @@ class CarrelStatusViewModel @Inject constructor( // Hydrate Device Token viewModelScope.launch { _deviceToken.update { - appPreference.getCachedToken() ?: "" + appTokenPreference.getCachedToken() ?: "" } } fetchCarrelRoomStateAndUpdateCache() diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt index da7a2ef3..4366e61f 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/CustomerServiceViewModel.kt @@ -3,7 +3,6 @@ package com.doyoonkim.main.viewmodel import android.os.Build import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.domain.interfaces.AppPreferenceRepository import com.doyoonkim.domain.usecases.SubmitUserReport import com.doyoonkim.main.contract.CustomerServiceEvent import com.doyoonkim.main.contract.CustomerServiceMutation @@ -16,7 +15,6 @@ import javax.inject.Inject class CustomerServiceViewModel @Inject constructor( private val submitUserReport: SubmitUserReport, - private val appPreferences: AppPreferenceRepository ) : BaseViewModel() { override fun setInitialState(): CustomerServiceStatus = CustomerServiceStatus() diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt index 3605bc7b..6e8b756c 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/HomeViewModel.kt @@ -6,7 +6,8 @@ import androidx.lifecycle.viewModelScope import com.doyoonkim.common.analytics.AnalyticsLogger import com.doyoonkim.common.base.BaseViewModel import com.doyoonkim.common.navigation.Destination -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.LocalWidgetCacheRepository import com.doyoonkim.domain.usecases.FetchTips import com.doyoonkim.domain.usecases.FetchTopThreeNotices @@ -30,7 +31,8 @@ class HomeViewModel @Inject constructor( private val localWidgetCacheRepository: LocalWidgetCacheRepository, private val fetchTopThreeNotices: FetchTopThreeNotices, private val fetchTips: FetchTips, - private val appPreferences: AppPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, + private val appWidgetPreference: AppWidgetPreferenceRepository, private val analytics: AnalyticsLogger ) : BaseViewModel() { override fun setInitialState(): HomeViewState = HomeViewState() @@ -150,7 +152,7 @@ class HomeViewModel @Inject constructor( // Side Effect (Access SharedPreference) private fun getMajorSubscriptionStatus() = viewModelScope.launch { - val subscribed = appPreferences.getSubscribedMajor()?.let { + val subscribed = appSubscriptionPreference.getSubscribedMajor()?.let { MajorCategory.valueOf(it) } Log.d(this.javaClass.name, "Subscription: $subscribed") @@ -172,7 +174,7 @@ class HomeViewModel @Inject constructor( private fun updateNoticeLocalCache(snapshot: HomeViewState) = viewModelScope.launch { - when (val noticePolicy = appPreferences.getWidgetCategoryPolicy()) { + when (val noticePolicy = appWidgetPreference.getWidgetCategoryPolicy()) { is WidgetCategoryPolicy.Main -> { localWidgetCacheRepository.updateNoticeCache( snapshot.mainContentState.get(noticePolicy.categoryKey) diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt index 0c4cba01..11153f16 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NoticeByMajorViewModel.kt @@ -3,7 +3,7 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository import com.doyoonkim.domain.usecases.FetchNoticesPerPage import com.doyoonkim.domain.usecases.SubmitNotificationPreferences import com.doyoonkim.main.contract.NoticeByMajorEvent @@ -19,7 +19,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class NoticeByMajorViewModel @Inject constructor( - private val appPreference: AppPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val fetchNoticesPerPage: FetchNoticesPerPage, private val submitNotificationPreferences: SubmitNotificationPreferences ): BaseViewModel() { @@ -64,7 +64,7 @@ class NoticeByMajorViewModel @Inject constructor( } private fun initialLoad() { - val currentSubscription = appPreference.getSubscribedMajor() + val currentSubscription = appSubscriptionPreference.getSubscribedMajor() currentSubscription?.let { subscribed -> mutate(NoticeByMajorMutation.Subscribed(MajorCategory.valueOf(subscribed))) } @@ -77,7 +77,7 @@ class NoticeByMajorViewModel @Inject constructor( private fun updateSubscribedMajor(newSubscription: MajorCategory) { mutate(NoticeByMajorMutation.Notices.Loading) - val prevSubscription = appPreference.getSubscribedMajor() + val prevSubscription = appSubscriptionPreference.getSubscribedMajor() // State Update & Request subscription update on Remote Source. viewModelScope.launch { val subscribeNewTopic = submitNotificationPreferences.invoke( @@ -101,7 +101,7 @@ class NoticeByMajorViewModel @Inject constructor( if (unsubscribePrevTopic) { // update - appPreference.updateSubscribedMajor(newSubscription.name) + appSubscriptionPreference.updateSubscribedMajor(newSubscription.name) mutate(NoticeByMajorMutation.Subscribed(newSubscription)) // Fetch new notices loadNotices() diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt index 1757f282..da10375f 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/NotificationPreferencesViewModel.kt @@ -8,7 +8,7 @@ import android.util.Log import androidx.core.content.ContextCompat import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository import com.doyoonkim.model.di.ApplicationContext import com.doyoonkim.domain.usecases.FetchTopicSubscriptionStatus import com.doyoonkim.domain.usecases.SubmitNotificationPreferences @@ -28,7 +28,7 @@ class NotificationPreferencesViewModel @Inject constructor( private val submitNotificationPreferences: SubmitNotificationPreferences, private val fetchTopicSubscriptionStatus: FetchTopicSubscriptionStatus, private val notificationManager: NotificationManager, - private val appPreferences: AppPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, @ApplicationContext private val context: Context ) : BaseViewModel() { private val TAG = this.javaClass.name @@ -103,7 +103,7 @@ class NotificationPreferencesViewModel @Inject constructor( } private fun updateMajorSubscriptionStatus(state: Boolean) { - val subscribedMajor = appPreferences.getSubscribedMajor() ?: return + val subscribedMajor = appSubscriptionPreference.getSubscribedMajor() ?: return mutate(NotificationPrefMutation.Syncing) viewModelScope.launch { @@ -166,12 +166,12 @@ class NotificationPreferencesViewModel @Inject constructor( .collectLatest { result -> result.fold( onSuccess = { status -> - val cachedMajorSelection = appPreferences.getSubscribedMajor() + val cachedMajorSelection = appSubscriptionPreference.getSubscribedMajor() if (cachedMajorSelection != null) { var majorStatus = false if (status.isNotEmpty()) { if (cachedMajorSelection != status.first().first) { - appPreferences.updateSubscribedMajor(status.first().first) + appSubscriptionPreference.updateSubscribedMajor(status.first().first) } majorStatus = status.first().second } diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt index 70cf48c0..9d042422 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SettingsViewModel.kt @@ -2,7 +2,7 @@ package com.doyoonkim.main.viewmodel import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository import com.doyoonkim.domain.usecases.SyncDataWithUpdateDatabase import com.doyoonkim.main.contract.SettingsEvent import com.doyoonkim.main.contract.SettingsMutation @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class SettingsViewModel @Inject constructor( - private val appPreferences: AppPreferenceRepository, + private val appDatabasePreference: AppDatabasePreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase ) : BaseViewModel() { private val TAG = this.javaClass.name @@ -22,7 +22,7 @@ class SettingsViewModel @Inject constructor( override fun handleEvent(event: SettingsEvent) { when (event) { is SettingsEvent.CheckDatabaseSyncStatus -> { - if (appPreferences.isPartialFailedDuringDatabaseSync()) + if (appDatabasePreference.isPartialFailedDuringDatabaseSync()) mutate(SettingsMutation.Database.SyncNeeded) } is SettingsEvent.RequestManualSync -> requestManualDatabaseSync() @@ -51,7 +51,7 @@ class SettingsViewModel @Inject constructor( syncDataWithUpdateDatabase.manualSync() .collectLatest { syncResult -> - appPreferences.setDatabaseSyncPartialFailedStatus(syncResult.withError) + appDatabasePreference.setDatabaseSyncPartialFailedStatus(syncResult.withError) mutate(SettingsMutation.Database.Synced(syncResult)) } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt index 12c34ff4..c006a26d 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/SplashViewModel.kt @@ -1,11 +1,10 @@ package com.doyoonkim.main.viewmodel -import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel import com.doyoonkim.common.di.TokenHandler -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppDatabasePreferenceRepository import com.doyoonkim.domain.interfaces.abtest.FirebaseRemoteConfigRepository import com.doyoonkim.domain.usecases.SyncDataWithUpdateDatabase import com.doyoonkim.main.contract.SplashEvent @@ -20,7 +19,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class SplashViewModel @Inject constructor( - private val appPreferences: AppPreferenceRepository, + private val appDatabasePreference: AppDatabasePreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase, private val tokenHandler: TokenHandler, private val remoteConfigRepository: FirebaseRemoteConfigRepository, @@ -47,13 +46,12 @@ class SplashViewModel @Inject constructor( } private fun checkDatabaseSyncStatus() { - if (appPreferences.isDatabaseSyncCompleted()) + if (appDatabasePreference.isDatabaseSyncCompleted()) mutate(SplashMutation.DatabaseSync.Completed) } private fun startPreprocess() = viewModelScope.launch { with(uiState.value) { - Log.d(TAG, "Current Token: ${appPreferences.getCachedToken()}") // Entry Token Validation val tokenResult = tokenHandler.validation() if (!tokenResult) tokenHandler.invoke() @@ -72,9 +70,9 @@ class SplashViewModel @Inject constructor( syncDataWithUpdateDatabase.entrySync() .collectLatest { result -> // result: Pair (SyncCompleted, PartialFailed) - appPreferences.setSyncStatus_1_2(result.completed) - appPreferences.setSyncStatus_2_3(result.completed) - appPreferences.setDatabaseSyncPartialFailedStatus(result.withError) + appDatabasePreference.setSyncStatus_1_2(result.completed) + appDatabasePreference.setSyncStatus_2_3(result.completed) + appDatabasePreference.setDatabaseSyncPartialFailedStatus(result.withError) if (result.completed) mutate(SplashMutation.DatabaseSync.Completed) } diff --git a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt index 4aeebb50..c010999d 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/viewmodel/WidgetConfigViewModel.kt @@ -3,7 +3,8 @@ package com.doyoonkim.main.viewmodel import android.util.Log import androidx.lifecycle.viewModelScope import com.doyoonkim.common.base.BaseViewModel -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.AsyncNoticeWidgetTaskScheduler import com.doyoonkim.main.contract.WidgetConfigEvent import com.doyoonkim.main.contract.WidgetConfigMutation @@ -20,7 +21,8 @@ import javax.inject.Inject * Created 2/16/26 at 8:38 PM */ class WidgetConfigViewModel @Inject constructor( - private val appPreference: AppPreferenceRepository, + private val appWidgetPreference: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val noticeWidgetTaskScheduler: AsyncNoticeWidgetTaskScheduler, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : BaseViewModel() { @@ -47,10 +49,10 @@ class WidgetConfigViewModel @Inject constructor( // Default options (Main Notice Categories) val default: List = NoticeCategory.entries.dropLast(1).map { it.name } mutate(WidgetConfigMutation.Configuration.Available( - default, appPreference.getSubscribedMajor() + default, appSubscriptionPreference.getSubscribedMajor() )) // Check Current Policy Status - val currentPolicy = appPreference.getWidgetCategoryPolicy() + val currentPolicy = appWidgetPreference.getWidgetCategoryPolicy() mutate(WidgetConfigMutation.Configuration.Selected(currentPolicy)) .also { Log.d(TAG, "Selected CategoryPolicy Set") } } @@ -59,7 +61,7 @@ class WidgetConfigViewModel @Inject constructor( mutate(WidgetConfigMutation.Configuration.Processing) viewModelScope.launch { // Update WidgetCategoryPolicy with Current Category Policy selection - appPreference.updateWidgetCategoryPolicy(uiState.value.selectedCategoryPolicy) + appWidgetPreference.updateWidgetCategoryPolicy(uiState.value.selectedCategoryPolicy) // Execute worker to update widget. noticeWidgetTaskScheduler.executeImmediateTask() mutate(WidgetConfigMutation.Configuration.Processed) diff --git a/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt b/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt index 8399c8af..c25be447 100644 --- a/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt +++ b/feature/widget/src/main/java/com/doyoonkim/widget/worker/KnuticeWidgetSync.kt @@ -7,7 +7,8 @@ import androidx.work.ListenableWorker import androidx.work.WorkerParameters import com.doyoonkim.model.di.ApplicationContext import com.doyoonkim.common.worker.IntermediateWorkerFactory -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository import com.doyoonkim.domain.interfaces.NoticeRemoteRepository import com.doyoonkim.model.NoticeVO import com.doyoonkim.model.WidgetCategoryPolicy @@ -19,7 +20,8 @@ import javax.inject.Inject class KnuticeWidgetSync( appContext: Context, workerParam: WorkerParameters, - private val appPreferences: AppPreferenceRepository, + private val appWidgetPreference: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : CoroutineWorker(appContext, workerParam) { @@ -30,7 +32,7 @@ class KnuticeWidgetSync( Log.d(TAG, "Worker Start") // Overall Logic: Fetch -> Debounce -> Validate -> Update -> Complete // Get Current Widget Category Policy - val widgetCategoryPolicy = appPreferences.getWidgetCategoryPolicy() + val widgetCategoryPolicy = appWidgetPreference.getWidgetCategoryPolicy() // If widgetCategory == null -> Set Widget State to Onboarding if (widgetCategoryPolicy is WidgetCategoryPolicy.Unconfigured) @@ -76,7 +78,7 @@ class KnuticeWidgetSync( } is WidgetCategoryPolicy.Major -> { // Get current subscription status. - val subscribedMajor = appPreferences.getSubscribedMajor() ?: return null + val subscribedMajor = appSubscriptionPreference.getSubscribedMajor() ?: return null category = subscribedMajor return remoteRepository.queryTopThreeNotices(subscribedMajor) } @@ -87,7 +89,8 @@ class KnuticeWidgetSync( // Factory class Factory @Inject constructor( @ApplicationContext private val context: Context, - private val appPreferences: AppPreferenceRepository, + private val appWidgetPreferences: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : IntermediateWorkerFactory { @@ -95,7 +98,8 @@ class KnuticeWidgetSync( return KnuticeWidgetSync( context, params, - appPreferences, + appWidgetPreferences, + appSubscriptionPreference, stateUpdater, remoteRepository ) From 791433250ad4f787f3b6c8597df1e825d2d1dafe Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Thu, 26 Mar 2026 00:09:22 -0400 Subject: [PATCH 10/11] KAN-111 [REFACTOR] Architectural Improvement on RemoteRepositories - Enhance current implementation of RemoteContentRepositoryImpl and RemotePreferenceRepositoryImpl into more smaller implementation of each interface to ensure strict separation of concern, and isolating logic/implementation * RemoteContentRepositoryImpl -> RemoteNoticeRepositoryImpl (Notice) -> RemoteTipRepositoryImpl (Tip) * RemotePreferenceRepositoryImpl -> RemoteSubscriptionRepositoryImpl (Major Subscriptions) -> RemoteUserReportRepositoryImpl (User Report Submission) --- .../com/doyoonkim/data/di/RemoteModules.kt | 14 +++--- .../remote/CampusRemoteRepository.kt | 4 +- ...yImpl.kt => RemoteNoticeRepositoryImpl.kt} | 22 +-------- ...kt => RemoteSubscriptionRepositoryImpl.kt} | 33 ++----------- .../remote/RemoteTipRepositoryImpl.kt | 37 +++++++++++++++ .../remote/RemoteTokenRepositoryImpl.kt | 4 +- .../remote/RemoteUserReportRepositoryImpl.kt | 47 +++++++++++++++++++ 7 files changed, 103 insertions(+), 58 deletions(-) rename core/data/src/main/java/com/doyoonkim/data/repository/remote/{RemoteContentRepositoryImpl.kt => RemoteNoticeRepositoryImpl.kt} (82%) rename core/data/src/main/java/com/doyoonkim/data/repository/remote/{RemotePreferenceRepositoryImpl.kt => RemoteSubscriptionRepositoryImpl.kt} (66%) create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTipRepositoryImpl.kt create mode 100644 core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteUserReportRepositoryImpl.kt diff --git a/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt b/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt index 8c67cdea..953d23fd 100644 --- a/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt +++ b/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt @@ -2,9 +2,11 @@ package com.doyoonkim.data.di import com.doyoonkim.data.repository.remote.CampusRemoteRepository import com.doyoonkim.data.repository.remote.ImageRepositoryImpl -import com.doyoonkim.data.repository.remote.RemoteContentRepositoryImpl -import com.doyoonkim.data.repository.remote.RemotePreferenceRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteNoticeRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteSubscriptionRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteTipRepositoryImpl import com.doyoonkim.data.repository.remote.RemoteTokenRepositoryImpl +import com.doyoonkim.data.repository.remote.RemoteUserReportRepositoryImpl import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.domain.interfaces.ImageRemoteRepository import com.doyoonkim.domain.interfaces.NoticeRemoteRepository @@ -19,7 +21,7 @@ import dagger.Module abstract class NoticeRemoteModule { @Binds abstract fun bindsNoticeRemoteRepo( - impl: RemoteContentRepositoryImpl + impl: RemoteNoticeRepositoryImpl ) : NoticeRemoteRepository } @@ -27,7 +29,7 @@ abstract class NoticeRemoteModule { abstract class TipRemoteModule { @Binds abstract fun bindsTipRemoteRepo( - impl: RemoteContentRepositoryImpl + impl: RemoteTipRepositoryImpl ) : TipRemoteRepository } @@ -43,12 +45,12 @@ abstract class TokenRemoteModule { abstract class PreferencesRemoteModule { @Binds abstract fun bindsTopicSubscriptionRemoteRepo( - impl: RemotePreferenceRepositoryImpl + impl: RemoteSubscriptionRepositoryImpl ) : TopicSubscriptionRemoteRepository @Binds abstract fun bindsUserReportRemoteRepo( - impl: RemotePreferenceRepositoryImpl + impl: RemoteUserReportRepositoryImpl ) : UserReportRemoteRepository } diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt index afedf59e..1f252afb 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/CampusRemoteRepository.kt @@ -1,7 +1,7 @@ package com.doyoonkim.data.repository.remote import android.util.Log -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository import com.doyoonkim.domain.interfaces.CarrelStatusRemoteRepository import com.doyoonkim.model.CarrelRoomStatusVO import com.doyoonkim.network.KnuticeRemoteSource @@ -12,7 +12,7 @@ import javax.inject.Inject class CampusRemoteRepository @Inject constructor( private val remoteSource: KnuticeRemoteSource, - private val appPreference: AppPreferenceRepository + private val appPreference: AppTokenPreferenceRepository ): CarrelStatusRemoteRepository { companion object { private val TAG = "CampusRemoteRepository" diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteNoticeRepositoryImpl.kt similarity index 82% rename from core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteNoticeRepositoryImpl.kt index 282be0e9..6d49390e 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteContentRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteNoticeRepositoryImpl.kt @@ -2,19 +2,15 @@ package com.doyoonkim.data.repository.remote import android.util.Log import com.doyoonkim.domain.interfaces.NoticeRemoteRepository -import com.doyoonkim.domain.interfaces.TipRemoteRepository import com.doyoonkim.model.NoticeVO -import com.doyoonkim.model.TipVO import com.doyoonkim.network.KnuticeRemoteSource import kotlinx.coroutines.flow.flow import model.Metadata import javax.inject.Inject -class RemoteContentRepositoryImpl @Inject constructor( +class RemoteNoticeRepositoryImpl @Inject constructor( private val remoteSource: KnuticeRemoteSource -) : NoticeRemoteRepository, - TipRemoteRepository -{ +) : NoticeRemoteRepository { private val TAG = "RemoteContentRepositoryImpl" override suspend fun queryTopThreeNotices(category: String): List? { @@ -87,20 +83,6 @@ class RemoteContentRepositoryImpl @Inject constructor( return null } - override fun queryAllTips() = flow { - remoteSource.getAllTips().fold( - onSuccess = { - if (it.result?.resultCode == 200) { - emit(it.body?.map { dto -> dto.toVO() }) - } else it.result.printLog().also { emit(emptyList()) } - }, - onFailure = { - it.printLog() - emit(null) - } - ) - } - private fun Throwable.printLog() = Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteSubscriptionRepositoryImpl.kt similarity index 66% rename from core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt rename to core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteSubscriptionRepositoryImpl.kt index 7eaf7d0b..00bbc1ff 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemotePreferenceRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteSubscriptionRepositoryImpl.kt @@ -1,25 +1,22 @@ package com.doyoonkim.data.repository.remote import android.util.Log -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository import com.doyoonkim.domain.interfaces.TopicSubscriptionRemoteRepository -import com.doyoonkim.domain.interfaces.UserReportRemoteRepository import com.doyoonkim.model.TopicType import com.doyoonkim.model.requestBody.TopicSubscriptionPreferencesBody -import com.doyoonkim.model.requestBody.UserReportBody import com.doyoonkim.network.KnuticeRemoteSource -import com.doyoonkim.network.model.ReportSaveRequest import com.doyoonkim.network.model.TopicUpdateRequest import kotlinx.coroutines.flow.flow import model.Metadata import javax.inject.Inject -class RemotePreferenceRepositoryImpl @Inject constructor( +class RemoteSubscriptionRepositoryImpl @Inject constructor( private val remoteSource: KnuticeRemoteSource, - private val appPreference: AppPreferenceRepository -) : TopicSubscriptionRemoteRepository, UserReportRemoteRepository { + private val appPreference: AppTokenPreferenceRepository +) : TopicSubscriptionRemoteRepository { - private val TAG = "RemotePreferenceRepositoryImpl" + private val TAG = "RemoteSubscriptionRepositoryImpl" override fun queryTopicSubscriptionStatus(topicType: TopicType) = flow { remoteSource.getTopicSubscriptionStatus( @@ -65,26 +62,6 @@ class RemotePreferenceRepositoryImpl @Inject constructor( ) } - override fun requestUserReportSubmission(body: UserReportBody) = flow { - remoteSource.submitUserReport( - appPreference.getCachedToken(), - ReportSaveRequest( - content = body.content, - deviceName = body.deviceName, - version = body.version - ) - ).fold( - onSuccess = { - if (it.result?.resultCode == 200) emit(true) - else it.result.printLog().also { emit(false) } - }, - onFailure = { - it.printLog() - emit(false) - } - ) - } - private fun Throwable.printLog() = Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTipRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTipRepositoryImpl.kt new file mode 100644 index 00000000..6de5dae0 --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTipRepositoryImpl.kt @@ -0,0 +1,37 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.TipRemoteRepository +import com.doyoonkim.model.TipVO +import com.doyoonkim.network.KnuticeRemoteSource +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemoteTipRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource +) : TipRemoteRepository { + + private val TAG = "RemoteTipRepositoryImpl" + + override fun queryAllTips() = flow { + remoteSource.getAllTips().fold( + onSuccess = { + if (it.result?.resultCode == 200) { + emit(it.body?.map { dto -> dto.toVO() }) + } else it.result.printLog().also { emit(emptyList()) } + }, + onFailure = { + it.printLog() + emit(null) + } + ) + } + + private fun Throwable.printLog() = + Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") + + private fun Metadata?.printLog() = + Log.d(TAG, "Failed to receive data (${this?.resultCode})" + + "\nREASON:${this?.resultMessage}") +} \ No newline at end of file diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt index f32ce3b6..1e2be67e 100644 --- a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.doyoonkim.data.repository.remote import android.util.Log -import com.doyoonkim.domain.interfaces.AppPreferenceRepository +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository import com.doyoonkim.domain.interfaces.TokenRemoteRepository import com.doyoonkim.model.TokenStatus import com.doyoonkim.model.requestBody.DeviceTokenBody @@ -16,7 +16,7 @@ import javax.inject.Inject class RemoteTokenRepositoryImpl @Inject constructor( private val remoteSource: KnuticeRemoteSource, - private val appPreference: AppPreferenceRepository + private val appPreference: AppTokenPreferenceRepository ) : TokenRemoteRepository { private val TAG = "RemoteTokenRepositoryImpl" diff --git a/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteUserReportRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteUserReportRepositoryImpl.kt new file mode 100644 index 00000000..62fc152c --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteUserReportRepositoryImpl.kt @@ -0,0 +1,47 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +import com.doyoonkim.domain.interfaces.UserReportRemoteRepository +import com.doyoonkim.model.requestBody.UserReportBody +import com.doyoonkim.network.KnuticeRemoteSource +import com.doyoonkim.network.model.ReportSaveRequest +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemoteUserReportRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource, + private val appPreference: AppTokenPreferenceRepository +) : UserReportRemoteRepository { + + private val TAG = "RemoteUserReportRepositoryImpl" + + override fun requestUserReportSubmission(body: UserReportBody) = flow { + remoteSource.submitUserReport( + appPreference.getCachedToken(), + ReportSaveRequest( + content = body.content, + deviceName = body.deviceName, + version = body.version + ) + ).fold( + onSuccess = { + if (it.result?.resultCode == 200) emit(true) + else it.result.printLog().also { emit(false) } + }, + onFailure = { + it.printLog() + emit(false) + } + ) + } + + private fun Throwable.printLog() = + Log.d(TAG, "Failed to receive data\nREASON: ${this.stackTraceToString()}") + + private fun Metadata?.printLog() = + Log.d(TAG, "Failed to receive data (${this?.resultCode})" + + "\nREASON:${this?.resultMessage}") + +} \ No newline at end of file From d3e5164c2dcf6dd9dbedd91c0c60bf3c21bf53e8 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Thu, 26 Mar 2026 00:27:42 -0400 Subject: [PATCH 11/11] KAN-111 [REFACTOR] Code Readability Enhancement based on Gemini Review. - Enhance Readability by using named Arguments and following function signature to provide arguments --- .../java/com/doyoonkim/knutice/MainApplication.kt | 11 +++++++++-- .../knutice/navigation/MainServiceNavGraph.kt | 4 ++-- .../knutice/navigation/campusServiceGraph.kt | 5 ++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt b/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt index 0dbc0919..22aea2a7 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt @@ -31,7 +31,11 @@ class MainApplication() : Application(), AppInjectorProvider, WidgetDependencyPr when(target) { is PushNotificationService -> { DaggerNotificationServiceComponent.factory() - .create(appComponent, appComponent, appComponent).inject(target) + .create( + systemServices = appComponent, + networkProvider = appComponent, + localPreferenceProvider = appComponent + ).inject(target) } else -> error("Unsupported Target $target") } @@ -41,7 +45,10 @@ class MainApplication() : Application(), AppInjectorProvider, WidgetDependencyPr override fun provide(): WidgetDependency { - return DaggerWidgetComponent.factory().create(appComponent, appComponent, appComponent) + return DaggerWidgetComponent.factory().create( + systemServices = appComponent, + networkProvider = appComponent, + localPreferenceProvider = appComponent) } @Inject lateinit var notificationManager: NotificationManager diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt index fb949048..2a585fde 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/MainServiceNavGraph.kt @@ -337,8 +337,8 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerSettingsSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localPreferenceProvider = appComponent, - localStorageProvider = appComponent + localStorageProvider = appComponent, + localPreferenceProvider = appComponent ) } diff --git a/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt b/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt index 5d6af441..5bdd7fec 100644 --- a/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt +++ b/app/src/main/java/com/doyoonkim/knutice/navigation/campusServiceGraph.kt @@ -44,7 +44,10 @@ fun NavGraphBuilder.campusServiceGraph( val sceneComponent = remember(appComponent) { DaggerCarrelStatusSceneComponent.factory().create( - appComponent, appComponent, appComponent, appComponent + systemServices = appComponent, + networkProvider = appComponent, + localCacheProvider = appComponent, + localPreferenceProvider = appComponent ) }