diff --git a/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt b/app/src/main/java/com/doyoonkim/knutice/MainApplication.kt index 1f72aa7c..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).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) + 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/SplashActivity.kt b/app/src/main/java/com/doyoonkim/knutice/SplashActivity.kt index 581bdc9b..4fb9cce6 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, + 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 d245bdf7..13287697 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, + 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 3e5a4736..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.common.di.AppPreferences +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: AppPreferences, + 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 bb61934b..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 @@ -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,15 @@ 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.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -41,6 +43,7 @@ import javax.inject.Singleton SystemCoroutineModule::class, LocalModule::class, RoomDatabaseModule::class, + LocalPreferenceModule::class, NetworkModule::class ] ) @@ -48,13 +51,12 @@ interface AppComponent : SystemServices, NetworkProvider, LocalStorageProvider, + LocalCacheProvider, + LocalPreferenceProvider, 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..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,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.LocalPreferenceProvider 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, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -32,7 +34,8 @@ interface NotificationServiceComponent { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + 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 a4338b60..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 @@ -16,6 +16,8 @@ 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.LocalPreferenceProvider import com.doyoonkim.knutice.di.util.LocalStorageProvider import com.doyoonkim.knutice.di.util.NetworkProvider import com.doyoonkim.knutice.di.util.SystemServices @@ -37,7 +39,8 @@ import javax.inject.Singleton @Component( dependencies = [ SystemServices::class, - LocalStorageProvider::class, + LocalCacheProvider::class, + LocalPreferenceProvider::class, NetworkProvider::class, FirebaseInfrastructureProvider::class ], @@ -58,14 +61,19 @@ interface HomeSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider, + localCacheProvider: LocalCacheProvider, + localPreferenceProvider: LocalPreferenceProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): HomeSceneComponent } } @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + NetworkProvider::class, + LocalPreferenceProvider::class + ], modules = [ ViewModelFactoryModule::class, NoticeByMajorSceneModule::class, @@ -82,14 +90,19 @@ interface NoticeByMajorSceneComponent { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + localPreferenceProvider: LocalPreferenceProvider ): NoticeByMajorSceneComponent } } @Component( - dependencies = [SystemServices::class, LocalStorageProvider::class], + dependencies = [ + SystemServices::class, + LocalStorageProvider::class, + LocalPreferenceProvider::class + ], modules = [ ViewModelFactoryModule::class, BookmarkListSceneModule::class, @@ -103,6 +116,7 @@ interface BookmarkListSceneComponent { interface Factory { fun create( systemServices: SystemServices, + localPreferenceProvider: LocalPreferenceProvider, localStorageProvider: LocalStorageProvider ): BookmarkListSceneComponent } @@ -212,7 +226,11 @@ interface NoticeInCategorySceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + LocalPreferenceProvider::class, + NetworkProvider::class + ], modules = [ ViewModelFactoryModule::class, CustomerServiceSceneModule::class, @@ -227,6 +245,7 @@ interface CustomerServiceSceneComponent { interface Factory { fun create( systemService: SystemServices, + localPreferenceProvider: LocalPreferenceProvider, networkProvider: NetworkProvider ): CustomerServiceSceneComponent } @@ -234,7 +253,11 @@ interface CustomerServiceSceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class], + dependencies = [ + SystemServices::class, + LocalPreferenceProvider::class, + NetworkProvider::class + ], modules = [ ViewModelFactoryModule::class, NotificationPreferencesSceneModule::class, @@ -249,6 +272,7 @@ interface NotificationPreferencesSceneComponent { interface Factory { fun create( systemServices: SystemServices, + localPreferenceProvider: LocalPreferenceProvider, networkProvider: NetworkProvider ): NotificationPreferencesSceneComponent } @@ -256,7 +280,12 @@ interface NotificationPreferencesSceneComponent { @Component( - dependencies = [SystemServices::class, NetworkProvider::class, LocalStorageProvider::class ], + dependencies = [ + SystemServices::class, + NetworkProvider::class, + LocalStorageProvider::class, + LocalPreferenceProvider::class + ], modules = [ ViewModelFactoryModule::class, SettingsSceneModule::class, @@ -272,7 +301,8 @@ interface SettingsSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider + localStorageProvider: LocalStorageProvider, + localPreferenceProvider: LocalPreferenceProvider ): SettingsSceneComponent } } @@ -283,6 +313,7 @@ interface SettingsSceneComponent { SystemServices::class, NetworkProvider::class, LocalStorageProvider::class, + LocalPreferenceProvider::class, FirebaseInfrastructureProvider::class ], modules = [ @@ -303,13 +334,14 @@ interface SplashSceneComponent { systemServices: SystemServices, networkProvider: NetworkProvider, localStorageProvider: LocalStorageProvider, + localPreferenceProvider: LocalPreferenceProvider, firebaseInfrastructureProvider: FirebaseInfrastructureProvider ): SplashSceneComponent } } @Component( - dependencies = [SystemServices::class], + dependencies = [SystemServices::class, LocalPreferenceProvider::class], modules = [ ViewModelFactoryModule::class, WidgetConfigSceneModule::class, @@ -322,7 +354,10 @@ interface WidgetConfigSceneComponent { @Component.Factory interface Factory { - fun create(systemService: SystemServices): WidgetConfigSceneComponent + fun create( + systemService: SystemServices, + localPreferenceProvider: LocalPreferenceProvider + ): WidgetConfigSceneComponent } } @@ -330,7 +365,8 @@ interface WidgetConfigSceneComponent { dependencies = [ SystemServices::class, NetworkProvider::class, - LocalStorageProvider::class + LocalCacheProvider::class, + LocalPreferenceProvider::class ], modules = [ ViewModelFactoryModule::class, @@ -346,7 +382,8 @@ interface CarrelStatusSceneComponent { fun create( systemServices: SystemServices, networkProvider: NetworkProvider, - localStorageProvider: LocalStorageProvider + 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 f1f6006f..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,6 +1,7 @@ package com.doyoonkim.knutice.di.components import com.doyoonkim.knutice.di.modules.WorkSchedulerModule +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 @@ -9,6 +10,7 @@ import dagger.Component @Component( dependencies = [ SystemServices::class, + LocalPreferenceProvider::class, NetworkProvider::class ], modules = [ @@ -21,7 +23,8 @@ interface WidgetComponent: WidgetDependency { interface Factory { fun create( systemServices: SystemServices, - networkProvider: NetworkProvider + networkProvider: NetworkProvider, + 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 2cb6b19b..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,6 +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.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 @@ -44,9 +48,19 @@ interface NetworkProvider { interface LocalStorageProvider { fun localNoticeRepository(): NoticeLocalRepository fun localBookmarkRepository(): BookmarkLocalRepository +} + +interface LocalCacheProvider { 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 8da9c3a8..2a585fde 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,8 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerHomeSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localStorageProvider = appComponent, + localCacheProvider = appComponent, + localPreferenceProvider = appComponent, firebaseInfrastructureProvider = appComponent ) } @@ -108,7 +108,8 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerNoticeByMajorSceneComponent.factory().create( systemServices = appComponent, - networkProvider = appComponent + networkProvider = appComponent, + localPreferenceProvider = appComponent ) } @@ -336,7 +337,8 @@ fun NavGraphBuilder.mainServiceNavGraph( DaggerSettingsSceneComponent.factory().create( systemServices = appComponent, networkProvider = appComponent, - localStorageProvider = appComponent + localStorageProvider = appComponent, + localPreferenceProvider = appComponent ) } @@ -374,6 +376,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerNotificationPreferencesSceneComponent.factory().create( systemServices = appComponent, + localPreferenceProvider = appComponent, networkProvider = appComponent ) } @@ -403,6 +406,7 @@ fun NavGraphBuilder.mainServiceNavGraph( val sceneComponent = remember(appComponent) { DaggerCustomerServiceSceneComponent.factory().create( systemService = 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 ff0909aa..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,6 +36,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( val sceneComponent = remember(appComponent) { DaggerBookmarkListSceneComponent.factory().create( systemServices = 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..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 + systemServices = appComponent, + networkProvider = appComponent, + localCacheProvider = appComponent, + localPreferenceProvider = appComponent ) } diff --git a/common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt b/common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt deleted file mode 100644 index 88408d40..00000000 --- a/common/src/main/java/com/doyoonkim/common/di/AppPreferences.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.doyoonkim.common.di - -import android.content.SharedPreferences -import javax.inject.Inject -import androidx.core.content.edit -import com.doyoonkim.model.WidgetCategoryPolicy -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -class AppPreferences @Inject constructor( - private val appPref: SharedPreferences -) { - 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. - */ - fun updateDeviceToken(token: String) { - appPref.edit { putString(DEVICE_TOKEN, token) } - } - - fun getCachedToken(): String? { - return appPref.getString(DEVICE_TOKEN, null) - } - - /** - * Major Subscription Status - */ - fun getSubscribedMajor(): String? { - return appPref.getString(SUBSCRIBED_MAJOR, null) - } - - fun updateSubscribedMajor(newMajor: String) { - appPref.edit { putString(SUBSCRIBED_MAJOR, newMajor) } - } - - /** - * Widget Configuration - */ - fun getWidgetCategoryPolicy(): WidgetCategoryPolicy { - val cachedPolicy = appPref.getString(WIDGET_CATEGORY, null) - ?: return WidgetCategoryPolicy.Unconfigured - - return json.decodeFromString(cachedPolicy) - } - - fun updateWidgetCategoryPolicy(policy: WidgetCategoryPolicy) { - val serializedString = json.encodeToString(policy) - appPref.edit { putString(WIDGET_CATEGORY, serializedString) } - } - - 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) - } - } - - fun isPartialFailedDuringDatabaseSync() = appPref.getBoolean(DB_SYNC_PARTIAL_FAIL, false) - - fun setDatabaseSyncStatus(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_STATUS, status) } - - fun setSyncStatus_1_2(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_1_2_STATUS, status) } - - fun setSyncStatus_2_3(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_2_3_STATUS, status) } - - fun setDatabaseSyncPartialFailedStatus(status: Boolean) = - appPref.edit { putBoolean(DB_SYNC_PARTIAL_FAIL, status) } - -} \ No newline at end of file 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 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..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,10 +1,18 @@ package com.doyoonkim.data.di import android.content.Context +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.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.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 @@ -46,4 +54,31 @@ object RoomDatabaseModule { @Singleton fun provideMainDatabaseDao(db: LocalDatabase) = db.getDao() +} + +@Module +abstract class LocalPreferenceModule { + @Binds + @Singleton + 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/di/RemoteModules.kt b/core/data/src/main/java/com/doyoonkim/data/di/RemoteModules.kt index a5725ce8..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 @@ -1,8 +1,12 @@ 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.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 @@ -17,7 +21,7 @@ import dagger.Module abstract class NoticeRemoteModule { @Binds abstract fun bindsNoticeRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteNoticeRepositoryImpl ) : NoticeRemoteRepository } @@ -25,7 +29,7 @@ abstract class NoticeRemoteModule { abstract class TipRemoteModule { @Binds abstract fun bindsTipRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteTipRepositoryImpl ) : TipRemoteRepository } @@ -33,7 +37,7 @@ abstract class TipRemoteModule { abstract class TokenRemoteModule { @Binds abstract fun bindsTokenRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteTokenRepositoryImpl ) : TokenRemoteRepository } @@ -41,12 +45,12 @@ abstract class TokenRemoteModule { abstract class PreferencesRemoteModule { @Binds abstract fun bindsTopicSubscriptionRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteSubscriptionRepositoryImpl ) : TopicSubscriptionRemoteRepository @Binds abstract fun bindsUserReportRemoteRepo( - impl: RemoteRepositoryImpl + impl: RemoteUserReportRepositoryImpl ) : 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/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/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/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 86% 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..28f42e09 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.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: AppPreferences + 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/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 82% 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..1f252afb 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.AppTokenPreferenceRepository 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: AppTokenPreferenceRepository ): 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/RemoteNoticeRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteNoticeRepositoryImpl.kt new file mode 100644 index 00000000..6d49390e --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteNoticeRepositoryImpl.kt @@ -0,0 +1,92 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.NoticeRemoteRepository +import com.doyoonkim.model.NoticeVO +import com.doyoonkim.network.KnuticeRemoteSource +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemoteNoticeRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource +) : NoticeRemoteRepository { + private val TAG = "RemoteContentRepositoryImpl" + + 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 + } + + 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/RemoteSubscriptionRepositoryImpl.kt b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteSubscriptionRepositoryImpl.kt new file mode 100644 index 00000000..00bbc1ff --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteSubscriptionRepositoryImpl.kt @@ -0,0 +1,71 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +import com.doyoonkim.domain.interfaces.TopicSubscriptionRemoteRepository +import com.doyoonkim.model.TopicType +import com.doyoonkim.model.requestBody.TopicSubscriptionPreferencesBody +import com.doyoonkim.network.KnuticeRemoteSource +import com.doyoonkim.network.model.TopicUpdateRequest +import kotlinx.coroutines.flow.flow +import model.Metadata +import javax.inject.Inject + +class RemoteSubscriptionRepositoryImpl @Inject constructor( + private val remoteSource: KnuticeRemoteSource, + private val appPreference: AppTokenPreferenceRepository +) : TopicSubscriptionRemoteRepository { + + private val TAG = "RemoteSubscriptionRepositoryImpl" + + 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) + } + ) + } + + 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/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 new file mode 100644 index 00000000..1e2be67e --- /dev/null +++ b/core/data/src/main/java/com/doyoonkim/data/repository/remote/RemoteTokenRepositoryImpl.kt @@ -0,0 +1,80 @@ +package com.doyoonkim.data.repository.remote + +import android.util.Log +import com.doyoonkim.domain.interfaces.AppTokenPreferenceRepository +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: AppTokenPreferenceRepository +) : TokenRemoteRepository { + + private val TAG = "RemoteTokenRepositoryImpl" + + 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 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 diff --git a/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt new file mode 100644 index 00000000..582b613b --- /dev/null +++ b/core/domain/src/main/java/com/doyoonkim/domain/interfaces/AppDatabasePreferenceRepository.kt @@ -0,0 +1,21 @@ +package com.doyoonkim.domain.interfaces + +import com.doyoonkim.model.WidgetCategoryPolicy + +/** + * @author kimdoyoon + * Created 3/25/26 at 1:42 AM + */ +interface AppDatabasePreferenceRepository { + + 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 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 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..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 @@ -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.AppTokenPreferenceRepository 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: AppTokenPreferenceRepository ) : TokenHandler { private val TAG = this.javaClass.name @@ -54,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 8017dc5c..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 @@ -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.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: AppPreferences, + 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 32b0ce9f..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.common.di.AppPreferences +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: AppPreferences + 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 85d3f6a8..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.common.di.AppPreferences 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: AppPreferences ) : 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..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 @@ -5,8 +5,9 @@ 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.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: AppPreferences, + 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 7f5c3374..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.common.di.AppPreferences +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: AppPreferences, + 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 dcd71b18..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 @@ -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.AppSubscriptionPreferenceRepository 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 appSubscriptionPreference: AppSubscriptionPreferenceRepository, @ApplicationContext private val context: Context ) : BaseViewModel() { private val TAG = this.javaClass.name @@ -104,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 { @@ -167,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 25fc2173..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 @@ -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.AppDatabasePreferenceRepository 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 appDatabasePreference: AppDatabasePreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase ) : BaseViewModel() { private val TAG = this.javaClass.name @@ -24,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() @@ -53,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 4c9f6abe..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,12 +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.AppPreferences -import com.doyoonkim.common.di.ApplicationScope import com.doyoonkim.common.di.TokenHandler +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 @@ -21,7 +19,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class SplashViewModel @Inject constructor( - private val appPreferences: AppPreferences, + private val appDatabasePreference: AppDatabasePreferenceRepository, private val syncDataWithUpdateDatabase: SyncDataWithUpdateDatabase, private val tokenHandler: TokenHandler, private val remoteConfigRepository: FirebaseRemoteConfigRepository, @@ -48,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() @@ -73,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) } @@ -83,17 +80,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..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.common.di.AppPreferences +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: AppPreferences, + 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 5b46ec18..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 @@ -2,31 +2,26 @@ 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.AppSubscriptionPreferenceRepository +import com.doyoonkim.domain.interfaces.AppWidgetPreferenceRepository 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 appWidgetPreference: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : CoroutineWorker(appContext, workerParam) { @@ -37,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) @@ -83,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) } @@ -94,7 +89,8 @@ class KnuticeWidgetSync( // Factory class Factory @Inject constructor( @ApplicationContext private val context: Context, - private val appPreferences: AppPreferences, + private val appWidgetPreferences: AppWidgetPreferenceRepository, + private val appSubscriptionPreference: AppSubscriptionPreferenceRepository, private val stateUpdater: WidgetStateUpdater, private val remoteRepository: NoticeRemoteRepository ) : IntermediateWorkerFactory { @@ -102,7 +98,8 @@ class KnuticeWidgetSync( return KnuticeWidgetSync( context, params, - appPreferences, + appWidgetPreferences, + appSubscriptionPreference, stateUpdater, remoteRepository )