diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 80c8eeb..a8319b7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -139,6 +139,9 @@ dependencies { implementation(libs.coil.compose) implementation(libs.lottie.compose) + // Datastore + implementation(libs.datastore.preferences) + // Unit Test testImplementation(libs.junit) testImplementation(libs.mockk) diff --git a/app/src/main/kotlin/com/whereismymotivation/WimmApplication.kt b/app/src/main/kotlin/com/whereismymotivation/WimmApplication.kt index cca2f47..cb33443 100644 --- a/app/src/main/kotlin/com/whereismymotivation/WimmApplication.kt +++ b/app/src/main/kotlin/com/whereismymotivation/WimmApplication.kt @@ -9,6 +9,9 @@ import com.whereismymotivation.init.FirebaseInit import com.whereismymotivation.init.MetricInit import com.whereismymotivation.init.WorkInit import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import javax.inject.Inject @HiltAndroidApp @@ -42,12 +45,15 @@ class WimmApplication : Application(), Configuration.Provider { .setWorkerFactory(workerFactory) .build() + @OptIn(DelicateCoroutinesApi::class) override fun onCreate() { super.onCreate() tracker.trackAppOpen() - metricInit.init() - workInit.init() - firebaseInit.init() - coilInit.init() + GlobalScope.launch { + metricInit.init() + workInit.init() + firebaseInit.init() + coilInit.init() + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/AppMetricPreferences.kt b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/AppMetricPreferences.kt index 6537a51..38992d9 100755 --- a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/AppMetricPreferences.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/AppMetricPreferences.kt @@ -1,48 +1,52 @@ package com.whereismymotivation.data.local.prefs -import android.content.SharedPreferences +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @Singleton -class AppMetricPreferences @Inject constructor(private val prefs: SharedPreferences) { +class AppMetricPreferences @Inject constructor(private val dataStore: DataStore) { companion object { - - private const val CURRENT_APP_VERSION = "PREF_KEY_CURRENT_APP_VERSION" - - private const val DAILY_RECORD_ALARM_TIME_HOUR = "PREF_KEY_DAILY_RECORD_ALARM_TIME_HOUR" - private const val DAILY_RECORD_ALARM_TIME_MIN = "PREF_KEY_DAILY_RECORD_ALARM_TIME_MIN" - private const val DAILY_RECORD_ALARM_TIME_SEC = "PREF_KEY_DAILY_RECORD_ALARM_TIME_SEC" - - private const val DAILY_MOOD_RECORDER_NOTIFICATION = - "PREF_KEY_DAILY_MOOD_RECORDER_NOTIFICATION" - + private val CURRENT_APP_VERSION = longPreferencesKey("CURRENT_APP_VERSION") + private val DAILY_RECORD_ALARM_TIME_HOUR = intPreferencesKey("DAILY_RECORD_ALARM_TIME_HOUR") + private val DAILY_RECORD_ALARM_TIME_MIN = intPreferencesKey("DAILY_RECORD_ALARM_TIME_MIN") + private val DAILY_RECORD_ALARM_TIME_SEC = intPreferencesKey("DAILY_RECORD_ALARM_TIME_SEC") + private val DAILY_MOOD_RECORDER_NOTIFICATION = + booleanPreferencesKey("DAILY_MOOD_RECORDER_NOTIFICATION") } - fun setCurrentAppVersion(appVersion: Long) = - prefs.edit().putLong(CURRENT_APP_VERSION, appVersion).apply() + suspend fun setCurrentAppVersion(appVersion: Long) { + dataStore.edit { it[CURRENT_APP_VERSION] = appVersion } + } - fun getCurrentAppVersion(): Long = - prefs.getLong(CURRENT_APP_VERSION, 0) + suspend fun getCurrentAppVersion() = + dataStore.data.map { it[CURRENT_APP_VERSION] ?: 0 }.first() - fun setDailyRecordAlarmTime(hour: Int, min: Int, sec: Int) { - prefs.edit().putInt(DAILY_RECORD_ALARM_TIME_HOUR, hour).apply() - prefs.edit().putInt(DAILY_RECORD_ALARM_TIME_MIN, min).apply() - prefs.edit().putInt(DAILY_RECORD_ALARM_TIME_SEC, sec).apply() + suspend fun setDailyRecordAlarmTime(hour: Int, min: Int, sec: Int) { + dataStore.edit { it[DAILY_RECORD_ALARM_TIME_HOUR] = hour } + dataStore.edit { it[DAILY_RECORD_ALARM_TIME_MIN] = min } + dataStore.edit { it[DAILY_RECORD_ALARM_TIME_SEC] = sec } } - fun getDailyRecordAlarmTime(): Triple { - val hour = prefs.getInt(DAILY_RECORD_ALARM_TIME_HOUR, 20) // 8PM - val min = prefs.getInt(DAILY_RECORD_ALARM_TIME_MIN, 0) - val sec = prefs.getInt(DAILY_RECORD_ALARM_TIME_SEC, 0) + suspend fun getDailyRecordAlarmTime(): Triple { + val hour = dataStore.data.map { it[DAILY_RECORD_ALARM_TIME_HOUR] ?: 22 }.first() // 8PM + val min = dataStore.data.map { it[DAILY_RECORD_ALARM_TIME_MIN] ?: 34 }.first() + val sec = dataStore.data.map { it[DAILY_RECORD_ALARM_TIME_SEC] ?: 0 }.first() return Triple(hour, min, sec) } - fun setDailyMoodRecorderNotificationEnable(enable: Boolean) = - prefs.edit().putBoolean(DAILY_MOOD_RECORDER_NOTIFICATION, enable).apply() - - fun getDailyMoodRecorderNotificationEnable() = - prefs.getBoolean(DAILY_MOOD_RECORDER_NOTIFICATION, true) + suspend fun setDailyMoodRecorderNotificationEnable(enable: Boolean) { + dataStore.edit { it[DAILY_MOOD_RECORDER_NOTIFICATION] = enable } + } + suspend fun getDailyMoodRecorderNotificationEnable() = + dataStore.data.map { it[DAILY_MOOD_RECORDER_NOTIFICATION] ?: true }.first() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/ContentPreferences.kt b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/ContentPreferences.kt index ffb4167..cae6b96 100755 --- a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/ContentPreferences.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/ContentPreferences.kt @@ -1,24 +1,32 @@ package com.whereismymotivation.data.local.prefs -import android.content.SharedPreferences +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import javax.inject.Inject -class ContentPreferences @Inject constructor(private val prefs: SharedPreferences) { +class ContentPreferences @Inject constructor(private val dataStore: DataStore) { companion object { - private const val FEED_NEXT_PAGE_NUMBER = "FEED_NEXT_PAGE_NUMBER" - private const val FEED_LAST_SEEN = "FEED_LAST_SEEN" + private val FEED_NEXT_PAGE_NUMBER = intPreferencesKey("FEED_NEXT_PAGE_NUMBER") + private val FEED_LAST_SEEN = longPreferencesKey("FEED_LAST_SEEN") } - fun getFeedNextPageNumber(): Int = - prefs.getInt(FEED_NEXT_PAGE_NUMBER, 1) + suspend fun getFeedNextPageNumber() = + dataStore.data.map { it[FEED_NEXT_PAGE_NUMBER] ?: 1 }.first() - fun setFeedNextPageNumber(pageNumber: Int) = - prefs.edit().putInt(FEED_NEXT_PAGE_NUMBER, pageNumber).apply() + suspend fun setFeedNextPageNumber(pageNumber: Int) { + dataStore.edit { it[FEED_NEXT_PAGE_NUMBER] = pageNumber } + } - fun getFeedLastSeen(): Long = - prefs.getLong(FEED_LAST_SEEN, System.currentTimeMillis()) + suspend fun getFeedLastSeen() = + dataStore.data.map { it[FEED_LAST_SEEN] ?: System.currentTimeMillis() }.first() - fun setFeedLastSeen(time: Long) = - prefs.edit().putLong(FEED_LAST_SEEN, time).apply() + suspend fun setFeedLastSeen(time: Long) { + dataStore.edit { it[FEED_LAST_SEEN] = time } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/UserPreferences.kt b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/UserPreferences.kt index 295a1b5..8b8935f 100755 --- a/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/UserPreferences.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/local/prefs/UserPreferences.kt @@ -1,114 +1,136 @@ package com.whereismymotivation.data.local.prefs -import android.content.SharedPreferences +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @Singleton -class UserPreferences @Inject constructor(private val prefs: SharedPreferences) { +class UserPreferences @Inject constructor(private val dataStore: DataStore) { companion object { - private const val ON_BOARDING_COMPLETED = "PREF_KEY_USER_ON_BOARDING_COMPLETED" - private const val USER_ID = "PREF_KEY_USER_ID" - private const val USER_NAME = "PREF_KEY_USER_NAME" - private const val USER_EMAIL = "PREF_KEY_USER_EMAIL" - private const val USER_PROFILE_PIC_URL = "USER_PROFILE_PIC_URL" - private const val ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN" - private const val REFRESH_TOKEN = "PREF_KEY_REFRESH_TOKEN" - private const val DEVICE_ID = "PREF_KEY_DEVICE_ID" - private const val USER_ROLES = "PREF_KEY_USER_ROLES" - private const val FIREBASE_TOKEN = "FIREBASE_TOKEN" - private const val FIREBASE_TOKEN_SENT = "FIREBASE_TOKEN_SENT" + private val ON_BOARDING_COMPLETED = booleanPreferencesKey("ON_BOARDING_COMPLETED") + private val USER_ID = stringPreferencesKey("USER_ID") + private val USER_NAME = stringPreferencesKey("USER_NAME") + private val USER_EMAIL = stringPreferencesKey("USER_EMAIL") + private val USER_PROFILE_PIC_URL = stringPreferencesKey("USER_PROFILE_PIC_URL") + private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN") + private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN") + private val DEVICE_ID = stringPreferencesKey("DEVICE_ID") + private val USER_ROLES = stringPreferencesKey("USER_ROLES") + private val FIREBASE_TOKEN = stringPreferencesKey("FIREBASE_TOKEN") + private val FIREBASE_TOKEN_SENT = booleanPreferencesKey("FIREBASE_TOKEN_SENT") } - fun getUserId(): String? = - prefs.getString(USER_ID, null) + suspend fun getUserId() = dataStore.data.map { it[USER_ID] }.first() - fun setUserId(userId: String) = - prefs.edit().putString(USER_ID, userId).apply() + suspend fun setUserId(userId: String) { + dataStore.edit { it[USER_ID] = userId } + } - fun removeUserId() = - prefs.edit().remove(USER_ID).apply() + suspend fun removeUserId() { + dataStore.edit { it.remove(USER_ID) } + } - fun getUserName(): String? = - prefs.getString(USER_NAME, null) + suspend fun getUserName() = dataStore.data.map { it[USER_NAME] }.first() - fun setUserName(userName: String) = - prefs.edit().putString(USER_NAME, userName).apply() + suspend fun setUserName(userName: String) { + dataStore.edit { it[USER_NAME] = userName } + } - fun removeUserName() = - prefs.edit().remove(USER_NAME).apply() + suspend fun removeUserName() { + dataStore.edit { it.remove(USER_NAME) } + } - fun getUserEmail(): String? = - prefs.getString(USER_EMAIL, null) + suspend fun getUserEmail() = dataStore.data.map { it[USER_EMAIL] }.first() - fun setUserEmail(email: String) = - prefs.edit().putString(USER_EMAIL, email).apply() + suspend fun setUserEmail(email: String) { + dataStore.edit { it[USER_EMAIL] = email } + } - fun removeUserEmail() = - prefs.edit().remove(USER_EMAIL).apply() + suspend fun removeUserEmail() { + dataStore.edit { it.remove(USER_EMAIL) } + } - fun getUserProfilePicUrlUrl(): String? = - prefs.getString(USER_PROFILE_PIC_URL, null) + suspend fun getUserProfilePicUrlUrl() = dataStore.data.map { it[USER_PROFILE_PIC_URL] }.first() - fun setUserProfileProfilePicUrl(url: String?) = - prefs.edit().putString(USER_PROFILE_PIC_URL, url).apply() + suspend fun setUserProfileProfilePicUrl(url: String?) { + url?.let { + dataStore.edit { it[USER_PROFILE_PIC_URL] = url } + } ?: removeUserProfilePicUrl() + } - fun removeUserProfilePicUrl() = - prefs.edit().remove(USER_PROFILE_PIC_URL).apply() + suspend fun removeUserProfilePicUrl() { + dataStore.edit { it.remove(USER_PROFILE_PIC_URL) } + } - fun getAccessToken(): String? = - prefs.getString(ACCESS_TOKEN, null) + suspend fun getAccessToken() = dataStore.data.map { it[ACCESS_TOKEN] }.first() - fun setAccessToken(token: String) = - prefs.edit().putString(ACCESS_TOKEN, token).apply() + suspend fun setAccessToken(token: String) { + dataStore.edit { it[ACCESS_TOKEN] = token } + } - fun removeAccessToken() = - prefs.edit().remove(ACCESS_TOKEN).apply() + suspend fun removeAccessToken() { + dataStore.edit { it.remove(ACCESS_TOKEN) } + } - fun getRefreshToken(): String? = - prefs.getString(REFRESH_TOKEN, null) + suspend fun getRefreshToken() = dataStore.data.map { it[REFRESH_TOKEN] }.first() - fun setRefreshToken(token: String) = - prefs.edit().putString(REFRESH_TOKEN, token).apply() + suspend fun setRefreshToken(token: String) { + dataStore.edit { it[REFRESH_TOKEN] = token } + } - fun removeRefreshToken() = - prefs.edit().remove(REFRESH_TOKEN).apply() + suspend fun removeRefreshToken() { + dataStore.edit { it.remove(REFRESH_TOKEN) } + } - fun getOnBoardingComplete(): Boolean = - prefs.getBoolean(ON_BOARDING_COMPLETED, false) + suspend fun getOnBoardingComplete() = + dataStore.data.map { it[ON_BOARDING_COMPLETED] }.first() ?: false - fun setOnBoardingComplete(complete: Boolean) = - prefs.edit().putBoolean(ON_BOARDING_COMPLETED, complete).apply() + suspend fun setOnBoardingComplete(complete: Boolean) { + dataStore.edit { it[ON_BOARDING_COMPLETED] = complete } + } - fun removeOnBoardingComplete() = - prefs.edit().remove(ON_BOARDING_COMPLETED).apply() + suspend fun removeOnBoardingComplete() { + dataStore.edit { it.remove(ON_BOARDING_COMPLETED) } + } - fun getDeviceId(): String? = - prefs.getString(DEVICE_ID, null) + suspend fun getDeviceId() = dataStore.data.map { it[DEVICE_ID] }.first() - fun setDeviceId(deviceId: String) = - prefs.edit().putString(DEVICE_ID, deviceId).apply() + suspend fun setDeviceId(deviceId: String) { + dataStore.edit { it[DEVICE_ID] = deviceId } + } - fun setFirebaseToken(token: String) = - prefs.edit().putString(FIREBASE_TOKEN, token).apply() + suspend fun setFirebaseToken(token: String) { + dataStore.edit { it[FIREBASE_TOKEN] = token } + } - fun getFirebaseToken(): String? = - prefs.getString(FIREBASE_TOKEN, null) + suspend fun getFirebaseToken() = dataStore.data.map { it[FIREBASE_TOKEN] }.first() - fun getFirebaseTokenSent(): Boolean = - prefs.getBoolean(FIREBASE_TOKEN_SENT, false) + suspend fun getFirebaseTokenSent() = + dataStore.data.map { it[FIREBASE_TOKEN_SENT] }.first() ?: false - fun setFirebaseTokenSent() = - prefs.edit().putBoolean(FIREBASE_TOKEN_SENT, true).apply() + suspend fun setFirebaseTokenSent() { + dataStore.edit { it[FIREBASE_TOKEN_SENT] = true } + } - fun removeFirebaseTokenSent() = - prefs.edit().remove(FIREBASE_TOKEN_SENT).apply() + suspend fun removeFirebaseTokenSent() { + dataStore.edit { it.remove(FIREBASE_TOKEN_SENT) } + } - fun getUserRoles(): String? = prefs.getString(USER_ROLES, null) + suspend fun getUserRoles() = dataStore.data.map { it[USER_ROLES] }.first() - fun setUserRoles(roles: String) = prefs.edit().putString(USER_ROLES, roles).apply() + suspend fun setUserRoles(roles: String) { + dataStore.edit { it[USER_ROLES] = roles } + } - fun removeUserRoles() = prefs.edit().remove(USER_ROLES).apply() + suspend fun removeUserRoles() { + dataStore.edit { it.remove(USER_ROLES) } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/data/remote/RequestHeaders.kt b/app/src/main/kotlin/com/whereismymotivation/data/remote/RequestHeaders.kt index 09a6534..d00ba84 100644 --- a/app/src/main/kotlin/com/whereismymotivation/data/remote/RequestHeaders.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/remote/RequestHeaders.kt @@ -5,14 +5,14 @@ import com.whereismymotivation.di.qualifier.AccessTokenInfo import com.whereismymotivation.di.qualifier.ApiKeyInfo import com.whereismymotivation.di.qualifier.AppVersionCodeInfo import com.whereismymotivation.di.qualifier.DeviceIdInfo -import com.whereismymotivation.utils.common.ResultFetcher +import com.whereismymotivation.utils.common.ResultFetcherBlocking import javax.inject.Inject class RequestHeaders @Inject constructor( @ApiKeyInfo val apiKey: String, - @AccessTokenInfo val accessTokenFetcher: ResultFetcher, - @DeviceIdInfo val deviceIdFetcher: ResultFetcher, - @AppVersionCodeInfo val appVersionCodeFetcher: ResultFetcher, + @AccessTokenInfo val accessTokenFetcher: ResultFetcherBlocking, + @DeviceIdInfo val deviceIdFetcher: ResultFetcherBlocking, + @AppVersionCodeInfo val appVersionCodeFetcher: ResultFetcherBlocking, ) { object Key { const val API_AUTH_TYPE = "API_AUTH_TYPE" diff --git a/app/src/main/kotlin/com/whereismymotivation/data/remote/interceptors/RefreshTokenInterceptor.kt b/app/src/main/kotlin/com/whereismymotivation/data/remote/interceptors/RefreshTokenInterceptor.kt index 11bfc87..d81d987 100644 --- a/app/src/main/kotlin/com/whereismymotivation/data/remote/interceptors/RefreshTokenInterceptor.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/remote/interceptors/RefreshTokenInterceptor.kt @@ -6,8 +6,8 @@ import com.whereismymotivation.data.remote.apis.auth.request.RefreshTokenRequest import com.whereismymotivation.data.remote.utils.ForcedLogout import com.whereismymotivation.di.qualifier.AccessTokenInfo import com.whereismymotivation.di.qualifier.RefreshTokenInfo -import com.whereismymotivation.utils.common.ResultCallback -import com.whereismymotivation.utils.common.ResultFetcher +import com.whereismymotivation.utils.common.ResultCallbackBlocking +import com.whereismymotivation.utils.common.ResultFetcherBlocking import okhttp3.Interceptor import okhttp3.Response import java.io.IOException @@ -17,10 +17,10 @@ import javax.inject.Singleton @Singleton class RefreshTokenInterceptor @Inject constructor( - @AccessTokenInfo private val accessTokenFetcher: ResultFetcher, - @AccessTokenInfo private val accessTokenCallback: ResultCallback, - @RefreshTokenInfo private val refreshTokenFetcher: ResultFetcher, - @RefreshTokenInfo private val refreshTokenCallback: ResultCallback, + @AccessTokenInfo private val accessTokenFetcher: ResultFetcherBlocking, + @AccessTokenInfo private val accessTokenCallback: ResultCallbackBlocking, + @RefreshTokenInfo private val refreshTokenFetcher: ResultFetcherBlocking, + @RefreshTokenInfo private val refreshTokenCallback: ResultCallbackBlocking, private val refreshTokenApi: RefreshTokenApi, private val forcedLogout: ForcedLogout, ) : Interceptor { diff --git a/app/src/main/kotlin/com/whereismymotivation/data/repository/AppMetricRepository.kt b/app/src/main/kotlin/com/whereismymotivation/data/repository/AppMetricRepository.kt index 3a6c723..5865022 100644 --- a/app/src/main/kotlin/com/whereismymotivation/data/repository/AppMetricRepository.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/repository/AppMetricRepository.kt @@ -9,7 +9,7 @@ class AppMetricRepository @Inject constructor( private val appMetricPreferences: AppMetricPreferences ) { - fun setCurrentAppVersion(appVersion: Long) { + suspend fun setCurrentAppVersion(appVersion: Long) { val lastAppVersion = getCurrentAppVersion() if (lastAppVersion != appVersion) { // app updated or first launch @@ -17,13 +17,13 @@ class AppMetricRepository @Inject constructor( } } - fun getCurrentAppVersion(): Long = + private suspend fun getCurrentAppVersion(): Long = appMetricPreferences.getCurrentAppVersion() - fun setDailyMoodRecorderNotificationEnable(enable: Boolean) = + suspend fun setDailyMoodRecorderNotificationEnable(enable: Boolean) = appMetricPreferences.setDailyMoodRecorderNotificationEnable(enable) - fun isDailyMoodRecorderNotificationEnabled() = + suspend fun isDailyMoodRecorderNotificationEnabled() = appMetricPreferences.getDailyMoodRecorderNotificationEnable() } diff --git a/app/src/main/kotlin/com/whereismymotivation/data/repository/ContentRepository.kt b/app/src/main/kotlin/com/whereismymotivation/data/repository/ContentRepository.kt index 2f40099..f1c8b86 100644 --- a/app/src/main/kotlin/com/whereismymotivation/data/repository/ContentRepository.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/repository/ContentRepository.kt @@ -89,14 +89,14 @@ class ContentRepository @Inject constructor( emit(contentApi.unpublishGeneral(ContentSubmissionRequest(contentId))) }.map { it.message } - fun getFeedNextPageNumber(): Int = contentPreferences.getFeedNextPageNumber() + suspend fun getFeedNextPageNumber(): Int = contentPreferences.getFeedNextPageNumber() - fun setFeedNextPageNumber(pageNumber: Int) = + suspend fun setFeedNextPageNumber(pageNumber: Int) = contentPreferences.setFeedNextPageNumber(pageNumber) - fun getFeedLastSeen(): Long = contentPreferences.getFeedLastSeen() + suspend fun getFeedLastSeen(): Long = contentPreferences.getFeedLastSeen() - fun markFeedLastSeen() = contentPreferences.setFeedLastSeen(System.currentTimeMillis()) + suspend fun markFeedLastSeen() = contentPreferences.setFeedLastSeen(System.currentTimeMillis()) suspend fun getContentDetails(contentId: String): Flow = flow { diff --git a/app/src/main/kotlin/com/whereismymotivation/data/repository/UserRepository.kt b/app/src/main/kotlin/com/whereismymotivation/data/repository/UserRepository.kt index 69c239f..ce42aaf 100644 --- a/app/src/main/kotlin/com/whereismymotivation/data/repository/UserRepository.kt +++ b/app/src/main/kotlin/com/whereismymotivation/data/repository/UserRepository.kt @@ -22,9 +22,9 @@ class UserRepository @Inject constructor( private val userPreferences: UserPreferences ) { - fun userExists() = userPreferences.getUserId() != null + suspend fun userExists() = userPreferences.getUserId() != null - fun saveCurrentAuth(auth: Auth) { + suspend fun saveCurrentAuth(auth: Auth) { removeCurrentUser() val user = auth.user userPreferences.setUserId(user.id) @@ -50,7 +50,7 @@ class UserRepository @Inject constructor( userPreferences.setUserProfileProfilePicUrl(user.profilePicUrl) } - fun removeCurrentUser() { + suspend fun removeCurrentUser() { userPreferences.removeUserId() userPreferences.removeUserName() userPreferences.removeUserEmail() @@ -62,8 +62,9 @@ class UserRepository @Inject constructor( userPreferences.removeFirebaseTokenSent() } - fun getCurrentUser(): User? { + suspend fun mustGetCurrentUser() = getCurrentUser()!! + suspend fun getCurrentUser(): User? { val userId = userPreferences.getUserId() val userName = userPreferences.getUserName() val userEmail = userPreferences.getUserEmail() @@ -91,16 +92,15 @@ class UserRepository @Inject constructor( return null } - fun markUserOnBoardingComplete() { + suspend fun markUserOnBoardingComplete() { userPreferences.setOnBoardingComplete(true) } - fun isOnBoardingComplete() = userPreferences.getOnBoardingComplete() + suspend fun isOnBoardingComplete() = userPreferences.getOnBoardingComplete() - fun saveDeviceId(deviceId: String) = userPreferences.setDeviceId(deviceId) - - fun getDeviceId() = userPreferences.getDeviceId() + suspend fun saveDeviceId(deviceId: String) = userPreferences.setDeviceId(deviceId) + suspend fun getDeviceId() = userPreferences.getDeviceId() suspend fun sendFirebaseToken(token: String): Flow = flow { @@ -112,11 +112,11 @@ class UserRepository @Inject constructor( emit(userApi.message(MessageRequest("USER_ANDROID_FEEDBACK", message))) }.map { it.message } - fun getFirebaseToken(): String? = userPreferences.getFirebaseToken() + suspend fun getFirebaseToken(): String? = userPreferences.getFirebaseToken() - fun setFirebaseToken(token: String) = userPreferences.setFirebaseToken(token) + suspend fun setFirebaseToken(token: String) = userPreferences.setFirebaseToken(token) - fun getFirebaseTokenSent(): Boolean = userPreferences.getFirebaseTokenSent() + suspend fun getFirebaseTokenSent(): Boolean = userPreferences.getFirebaseTokenSent() - fun setFirebaseTokenSent() = userPreferences.setFirebaseTokenSent() + suspend fun setFirebaseTokenSent() = userPreferences.setFirebaseTokenSent() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/di/module/ApplicationModule.kt b/app/src/main/kotlin/com/whereismymotivation/di/module/ApplicationModule.kt index 0bb294c..bba92a3 100644 --- a/app/src/main/kotlin/com/whereismymotivation/di/module/ApplicationModule.kt +++ b/app/src/main/kotlin/com/whereismymotivation/di/module/ApplicationModule.kt @@ -14,7 +14,7 @@ import com.whereismymotivation.data.local.prefs.AppMetricPreferences import com.whereismymotivation.data.local.prefs.UserPreferences import com.whereismymotivation.di.qualifier.AppVersionCodeInfo import com.whereismymotivation.di.qualifier.DeviceIdInfo -import com.whereismymotivation.utils.common.ResultFetcher +import com.whereismymotivation.utils.common.ResultFetcherBlocking import com.whereismymotivation.utils.config.RemoteKey import com.whereismymotivation.utils.log.Logger import com.whereismymotivation.work.AppWorkManager @@ -23,6 +23,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking import javax.inject.Singleton @Module @@ -34,8 +35,8 @@ object ApplicationModule { @DeviceIdInfo fun provideDeviceId( userPreferences: UserPreferences - ): ResultFetcher = object : ResultFetcher { - override fun fetch(): String? = userPreferences.getDeviceId() + ): ResultFetcherBlocking = object : ResultFetcherBlocking { + override fun fetch(): String? = runBlocking { userPreferences.getDeviceId() } } @Provides @@ -43,8 +44,8 @@ object ApplicationModule { @AppVersionCodeInfo fun provideAppVersionCode( appMetricPreferences: AppMetricPreferences - ): ResultFetcher = object : ResultFetcher { - override fun fetch(): Long = appMetricPreferences.getCurrentAppVersion() + ): ResultFetcherBlocking = object : ResultFetcherBlocking { + override fun fetch(): Long = runBlocking { appMetricPreferences.getCurrentAppVersion() } } @Provides diff --git a/app/src/main/kotlin/com/whereismymotivation/di/module/LocalDataModule.kt b/app/src/main/kotlin/com/whereismymotivation/di/module/LocalDataModule.kt index a9c83d9..7b49569 100644 --- a/app/src/main/kotlin/com/whereismymotivation/di/module/LocalDataModule.kt +++ b/app/src/main/kotlin/com/whereismymotivation/di/module/LocalDataModule.kt @@ -1,11 +1,12 @@ package com.whereismymotivation.di.module import android.content.Context -import android.content.SharedPreferences +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore import androidx.room.Room import com.whereismymotivation.data.local.db.DatabaseService import com.whereismymotivation.di.qualifier.DatabaseInfo -import com.whereismymotivation.di.qualifier.PrefsInfo import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -13,6 +14,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton +val Context.dataStore: DataStore by preferencesDataStore(name = "wimm-prefs") + @Module @InstallIn(SingletonComponent::class) object LocalDataModule { @@ -24,15 +27,9 @@ object LocalDataModule { @Provides @Singleton - @PrefsInfo - fun providePreferenceName(): String = "wimm-prefs" - - @Provides - @Singleton - fun provideSharedPreferences( + fun provideDataStorePreferences( @ApplicationContext context: Context, - @PrefsInfo prefName: String - ): SharedPreferences = context.getSharedPreferences(prefName, Context.MODE_PRIVATE) + ): DataStore = context.dataStore @Provides @Singleton diff --git a/app/src/main/kotlin/com/whereismymotivation/di/module/NetworkModule.kt b/app/src/main/kotlin/com/whereismymotivation/di/module/NetworkModule.kt index 6e38159..7896f78 100644 --- a/app/src/main/kotlin/com/whereismymotivation/di/module/NetworkModule.kt +++ b/app/src/main/kotlin/com/whereismymotivation/di/module/NetworkModule.kt @@ -21,13 +21,14 @@ import com.whereismymotivation.di.qualifier.AccessTokenInfo import com.whereismymotivation.di.qualifier.ApiKeyInfo import com.whereismymotivation.di.qualifier.BaseUrl import com.whereismymotivation.di.qualifier.RefreshTokenInfo -import com.whereismymotivation.utils.common.ResultCallback -import com.whereismymotivation.utils.common.ResultFetcher +import com.whereismymotivation.utils.common.ResultCallbackBlocking +import com.whereismymotivation.utils.common.ResultFetcherBlocking import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking import okhttp3.OkHttpClient import javax.inject.Singleton @@ -163,8 +164,8 @@ object NetworkModule { @AccessTokenInfo fun provideAccessToken( userPreferences: UserPreferences - ): ResultFetcher = object : ResultFetcher { - override fun fetch(): String? = userPreferences.getAccessToken() + ): ResultFetcherBlocking = object : ResultFetcherBlocking { + override fun fetch(): String? = runBlocking { userPreferences.getAccessToken() } } @Provides @@ -172,8 +173,8 @@ object NetworkModule { @RefreshTokenInfo fun provideRefreshToken( userPreferences: UserPreferences - ): ResultFetcher = object : ResultFetcher { - override fun fetch(): String? = userPreferences.getRefreshToken() + ): ResultFetcherBlocking = object : ResultFetcherBlocking { + override fun fetch(): String? = runBlocking { userPreferences.getRefreshToken() } } @Provides @@ -181,8 +182,9 @@ object NetworkModule { @AccessTokenInfo fun provideAccessTokenSaveLambda( userPreferences: UserPreferences - ): ResultCallback = object : ResultCallback { - override fun onResult(result: String) = userPreferences.setAccessToken(result) + ): ResultCallbackBlocking = object : ResultCallbackBlocking { + override fun onResult(result: String) = + runBlocking { userPreferences.setAccessToken(result) } } @Provides @@ -190,7 +192,8 @@ object NetworkModule { @RefreshTokenInfo fun provideRefreshTokenSaveLambda( userPreferences: UserPreferences - ): ResultCallback = object : ResultCallback { - override fun onResult(result: String) = userPreferences.setRefreshToken(result) + ): ResultCallbackBlocking = object : ResultCallbackBlocking { + override fun onResult(result: String) = + runBlocking { userPreferences.setRefreshToken(result) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/di/qualifier/PrefsInfo.kt b/app/src/main/kotlin/com/whereismymotivation/di/qualifier/PrefsInfo.kt deleted file mode 100644 index c750a9c..0000000 --- a/app/src/main/kotlin/com/whereismymotivation/di/qualifier/PrefsInfo.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.whereismymotivation.di.qualifier - -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.RUNTIME) -annotation class PrefsInfo \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/init/CoilInit.kt b/app/src/main/kotlin/com/whereismymotivation/init/CoilInit.kt index 0b0d88e..9abcf8b 100644 --- a/app/src/main/kotlin/com/whereismymotivation/init/CoilInit.kt +++ b/app/src/main/kotlin/com/whereismymotivation/init/CoilInit.kt @@ -8,8 +8,8 @@ import javax.inject.Singleton @Singleton class CoilInit @Inject constructor( private val imageLoader: ImageLoader -): Initializer { - override fun init() { +) : Initializer { + override suspend fun init() { Coil.setImageLoader(imageLoader) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/init/FirebaseInit.kt b/app/src/main/kotlin/com/whereismymotivation/init/FirebaseInit.kt index f0c2e8c..caafa46 100644 --- a/app/src/main/kotlin/com/whereismymotivation/init/FirebaseInit.kt +++ b/app/src/main/kotlin/com/whereismymotivation/init/FirebaseInit.kt @@ -27,13 +27,13 @@ class FirebaseInit @Inject constructor( @ApplicationContext private val context: Context, private val userRepository: UserRepository, ) : Initializer { - override fun init() { + override suspend fun init() { recordUser() syncFcmToken() createDefaultNotificationChannel() } - private fun recordUser() { + private suspend fun recordUser() { userRepository.getCurrentUser()?.run { Firebase.crashlytics.setUserId(id) Firebase.analytics.setUserId(id) @@ -43,7 +43,7 @@ class FirebaseInit @Inject constructor( } @OptIn(DelicateCoroutinesApi::class) - private fun syncFcmToken() { + private suspend fun syncFcmToken() { if (!userRepository.getFirebaseTokenSent() && userRepository.userExists()) { FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (!task.isSuccessful) { @@ -54,19 +54,18 @@ class FirebaseInit @Inject constructor( ) return@addOnCompleteListener } - val token = task.result - - userRepository.setFirebaseToken(token) - - GlobalScope.launch { - userRepository.sendFirebaseToken(token) - .catch { } - .collect { - userRepository.setFirebaseTokenSent() - } + task.result?.let { + GlobalScope.launch { + userRepository.setFirebaseToken(it) + userRepository.sendFirebaseToken(it) + .catch { e -> + Logger.record(e) + }.collect { + userRepository.setFirebaseTokenSent() + } + } + if (BuildConfig.DEBUG) Logger.d(WimmApplication.TAG, it) } - - if (BuildConfig.DEBUG) Logger.d(WimmApplication.TAG, token) } } } @@ -78,25 +77,23 @@ class FirebaseInit @Inject constructor( // or other notification behaviors after this val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannel( - NotificationChannel( - context.getString(R.string.default_notification_channel_id), - context.getString(R.string.notification_default_channel_name), - NotificationManager.IMPORTANCE_DEFAULT - ).apply { - description = - context.getString(R.string.notification_default_channel_description) - }) + notificationManager.createNotificationChannel(NotificationChannel( + context.getString(R.string.default_notification_channel_id), + context.getString(R.string.notification_default_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = + context.getString(R.string.notification_default_channel_description) + }) - notificationManager.createNotificationChannel( - NotificationChannel( - context.getString(R.string.happiness_notification_channel_id), - context.getString(R.string.notification_happiness_channel_name), - NotificationManager.IMPORTANCE_DEFAULT - ).apply { - description = - context.getString(R.string.notification_happiness_channel_description) - }) + notificationManager.createNotificationChannel(NotificationChannel( + context.getString(R.string.happiness_notification_channel_id), + context.getString(R.string.notification_happiness_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = + context.getString(R.string.notification_happiness_channel_description) + }) } } catch (e: Exception) { Logger.record(e) diff --git a/app/src/main/kotlin/com/whereismymotivation/init/Initializer.kt b/app/src/main/kotlin/com/whereismymotivation/init/Initializer.kt index 131b8ed..b6e2ccb 100644 --- a/app/src/main/kotlin/com/whereismymotivation/init/Initializer.kt +++ b/app/src/main/kotlin/com/whereismymotivation/init/Initializer.kt @@ -1,5 +1,5 @@ package com.whereismymotivation.init interface Initializer { - fun init() + suspend fun init() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/init/MetricInit.kt b/app/src/main/kotlin/com/whereismymotivation/init/MetricInit.kt index 8f86c8a..33d3bc7 100644 --- a/app/src/main/kotlin/com/whereismymotivation/init/MetricInit.kt +++ b/app/src/main/kotlin/com/whereismymotivation/init/MetricInit.kt @@ -12,7 +12,7 @@ class MetricInit @Inject constructor( @ApplicationContext private val context: Context, private var appMetricRepository: AppMetricRepository ) : Initializer { - override fun init() { + override suspend fun init() { appMetricRepository.setCurrentAppVersion(SystemUtils.getAppVersionCode(context)) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/init/WorkInit.kt b/app/src/main/kotlin/com/whereismymotivation/init/WorkInit.kt index 6506821..b80eeaf 100644 --- a/app/src/main/kotlin/com/whereismymotivation/init/WorkInit.kt +++ b/app/src/main/kotlin/com/whereismymotivation/init/WorkInit.kt @@ -12,11 +12,11 @@ class WorkInit @Inject constructor( private val alarmManager: AppAlarmManager, private val appMetricPreferences: AppMetricPreferences ) : Initializer { - override fun init() { + override suspend fun init() { scheduleWorks() } - private fun scheduleWorks() { + private suspend fun scheduleWorks() { val time = appMetricPreferences.getDailyRecordAlarmTime() alarmManager.setDailyMoodAlarm(time.first, time.second, time.third) diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/feed/FeedViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/feed/FeedViewModel.kt index 3beb4e0..5bdc3d6 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/feed/FeedViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/feed/FeedViewModel.kt @@ -15,6 +15,7 @@ import com.whereismymotivation.ui.navigation.Navigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject @HiltViewModel @@ -38,11 +39,12 @@ class FeedViewModel @Inject constructor( private var loading = false private var pageItemCount = remoteConfigRepository.getHomePageContentCount() - private var startPageNumber = contentRepository.getFeedNextPageNumber() + private var startPageNumber = runBlocking { contentRepository.getFeedNextPageNumber() } private var currentPageNumber = startPageNumber init { - if ((System.currentTimeMillis() - contentRepository.getFeedLastSeen()) / 1000 / 60 / 60 > 3) { + val lastSeenTime = runBlocking { contentRepository.getFeedLastSeen() } + if ((System.currentTimeMillis() - lastSeenTime) / 1000 / 60 / 60 > 3) { rotateFeedList() } @@ -53,7 +55,7 @@ class FeedViewModel @Inject constructor( } private fun rotateFeedList(reload: Boolean = false) { - contentRepository.setFeedNextPageNumber(1) + viewModelScope.launch { contentRepository.setFeedNextPageNumber(1) } startPageNumber = 1 currentPageNumber = startPageNumber if (reload) loadMoreContents() @@ -114,7 +116,7 @@ class FeedViewModel @Inject constructor( } override fun onCleared() { - contentRepository.markFeedLastSeen() + viewModelScope.launch { contentRepository.markFeedLastSeen() } super.onCleared() } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/journal/JournalsViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/journal/JournalsViewModel.kt index e17d273..da6efcf 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/journal/JournalsViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/journal/JournalsViewModel.kt @@ -24,7 +24,7 @@ import javax.inject.Inject class JournalsViewModel @Inject constructor( loader: Loader, navigator: Navigator, - userRepository: UserRepository, + val userRepository: UserRepository, val messenger: Messenger, private val journalRepository: JournalRepository ) : BaseViewModel(loader, messenger, navigator) { @@ -33,8 +33,6 @@ class JournalsViewModel @Inject constructor( const val TAG = "JournalsViewModel" } - private val user = userRepository.getCurrentUser()!! - private val pageItemCount = 10 private var loading = false private var currentPageNumber = 1 @@ -49,6 +47,7 @@ class JournalsViewModel @Inject constructor( if (loading) return loading = true viewModelScope.launch { + val user = userRepository.mustGetCurrentUser() journalRepository .fetchJournals(user.id, pageNumber, pageItemCount) .catch { @@ -93,6 +92,7 @@ class JournalsViewModel @Inject constructor( return messenger.deliverRes(Message.error(R.string.record_can_not_empty)) viewModelScope.launch { + val user = userRepository.mustGetCurrentUser() val now = CalendarUtils.now() val journal = Journal(0, String.Null(), text, user.id, now, now) journalRepository diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/moods/MoodsViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/moods/MoodsViewModel.kt index 67bf42d..b9329da 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/moods/MoodsViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/moods/MoodsViewModel.kt @@ -22,7 +22,7 @@ import javax.inject.Inject class MoodsViewModel @Inject constructor( loader: Loader, navigator: Navigator, - userRepository: UserRepository, + val userRepository: UserRepository, val messenger: Messenger, private val moodRepository: MoodRepository ) : BaseViewModel(loader, messenger, navigator) { @@ -31,7 +31,6 @@ class MoodsViewModel @Inject constructor( const val TAG = "MoodsViewModel" } - private val user = userRepository.getCurrentUser()!! private val moods = mutableStateListOf() private val _moodGraph = mutableStateListOf() @@ -44,6 +43,7 @@ class MoodsViewModel @Inject constructor( private fun loadMoods() { viewModelScope.launch { + val user = userRepository.mustGetCurrentUser() moodRepository.fetchMoods(user.id) .catch { messenger.deliverRes(Message.error(R.string.something_went_wrong)) @@ -76,6 +76,7 @@ class MoodsViewModel @Inject constructor( fun selectMood(code: Mood.Code) { viewModelScope.launch { + val user = userRepository.mustGetCurrentUser() val now = CalendarUtils.now() val mood = Mood(0, String.Null(), code, user.id, now, now) val dateStr = CalendarUtils.getFormattedDate(now) ?: "Unknown" diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/mybox/MyBoxViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/mybox/MyBoxViewModel.kt index 5024e28..4d3df6d 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/mybox/MyBoxViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/mybox/MyBoxViewModel.kt @@ -13,6 +13,7 @@ import com.whereismymotivation.ui.navigation.Navigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.runBlocking import javax.inject.Inject @HiltViewModel @@ -29,7 +30,7 @@ class MyBoxViewModel @Inject constructor( const val TAG = "MyBoxViewModel" } - private val _user = MutableStateFlow(userRepository.getCurrentUser()!!) + private val _user = MutableStateFlow(runBlocking { userRepository.mustGetCurrentUser() }) private val _contents = mutableStateListOf() val user = _user.asStateFlow() diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/profile/ProfileViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/profile/ProfileViewModel.kt index 59a92ac..5d8c749 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/profile/ProfileViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/profile/ProfileViewModel.kt @@ -1,7 +1,6 @@ package com.whereismymotivation.ui.profile import androidx.lifecycle.SavedStateHandle -import com.whereismymotivation.R import com.whereismymotivation.data.repository.AuthRepository import com.whereismymotivation.data.repository.UserRepository import com.whereismymotivation.ui.base.BaseViewModel @@ -13,6 +12,7 @@ import com.whereismymotivation.ui.navigation.Navigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.runBlocking import javax.inject.Inject @HiltViewModel @@ -31,11 +31,12 @@ class ProfileViewModel @Inject constructor( private var _selectedTab = MutableStateFlow( ProfileTab.fromName( - savedStateHandle.get(Destination.Home.Profile.routeArgName) ?: ProfileTab.MOOD.name + savedStateHandle.get(Destination.Home.Profile.routeArgName) + ?: ProfileTab.MOOD.name ) ) - private val _user = MutableStateFlow(userRepository.getCurrentUser()!!) + private val _user = MutableStateFlow(runBlocking { userRepository.mustGetCurrentUser() }) val user = _user.asStateFlow() val selectedTab = _selectedTab.asStateFlow() @@ -45,18 +46,13 @@ class ProfileViewModel @Inject constructor( } fun logout() { - val exists = userRepository.userExists() - if (exists) { - launchNetwork { - authRepository.logout() - .collect { - userRepository.removeCurrentUser() - navigator.navigateTo(Destination.Login.route, true) - messenger.deliver(Message.success("Logout Success")) - } - } - } else { - messenger.deliverRes(Message.error(R.string.something_went_wrong)) + launchNetwork { + authRepository.logout() + .collect { + userRepository.removeCurrentUser() + navigator.navigateTo(Destination.Login.route, true) + messenger.deliver(Message.success("Logout Success")) + } } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/ui/splash/SplashViewModel.kt b/app/src/main/kotlin/com/whereismymotivation/ui/splash/SplashViewModel.kt index 5fed185..641b670 100644 --- a/app/src/main/kotlin/com/whereismymotivation/ui/splash/SplashViewModel.kt +++ b/app/src/main/kotlin/com/whereismymotivation/ui/splash/SplashViewModel.kt @@ -1,5 +1,6 @@ package com.whereismymotivation.ui.splash +import androidx.lifecycle.viewModelScope import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.whereismymotivation.data.repository.UserRepository import com.whereismymotivation.ui.base.BaseViewModel @@ -8,6 +9,7 @@ import com.whereismymotivation.ui.common.snackbar.Messenger import com.whereismymotivation.ui.navigation.Destination import com.whereismymotivation.ui.navigation.Navigator import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -26,15 +28,17 @@ class SplashViewModel @Inject constructor( init { // TODO: think of this for open source use case firebaseRemote.ensureInitialized().addOnCompleteListener { - val exists = userRepository.userExists() - if (exists) { - if (userRepository.isOnBoardingComplete()) { - navigator.navigateTo(Destination.Home.route, true) + viewModelScope.launch { + val exists = userRepository.userExists() + if (exists) { + if (userRepository.isOnBoardingComplete()) { + navigator.navigateTo(Destination.Home.route, true) + } else { + navigator.navigateTo(Destination.Onboarding.route, true) + } } else { - navigator.navigateTo(Destination.Onboarding.route, true) + navigator.navigateTo(Destination.Login.route, true) } - } else { - navigator.navigateTo(Destination.Login.route, true) } } } diff --git a/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultCallback.kt b/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultCallback.kt index 5365468..4383ce9 100644 --- a/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultCallback.kt +++ b/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultCallback.kt @@ -1,5 +1,9 @@ package com.whereismymotivation.utils.common interface ResultCallback { + suspend fun onResult(result: T) +} + +interface ResultCallbackBlocking { fun onResult(result: T) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultFetcher.kt b/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultFetcher.kt index ef4e76e..96b519b 100644 --- a/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultFetcher.kt +++ b/app/src/main/kotlin/com/whereismymotivation/utils/common/ResultFetcher.kt @@ -1,5 +1,9 @@ package com.whereismymotivation.utils.common interface ResultFetcher { + suspend fun fetch(): T? +} + +interface ResultFetcherBlocking { fun fetch(): T? } \ No newline at end of file diff --git a/app/src/main/kotlin/com/whereismymotivation/work/AppAlarmManager.kt b/app/src/main/kotlin/com/whereismymotivation/work/AppAlarmManager.kt index 7f97404..ef50f41 100644 --- a/app/src/main/kotlin/com/whereismymotivation/work/AppAlarmManager.kt +++ b/app/src/main/kotlin/com/whereismymotivation/work/AppAlarmManager.kt @@ -23,7 +23,7 @@ class AppAlarmManager @Inject constructor( const val ACTION_DAILY_MOOD_RECORD = "ACTION_DAILY_MOOD_RECORD" } - fun setDailyMoodAlarm(hour: Int, min: Int, sec: Int) { + suspend fun setDailyMoodAlarm(hour: Int, min: Int, sec: Int) { if (!appMetricRepository.isDailyMoodRecorderNotificationEnabled()) return try { diff --git a/app/src/test/java/com/whereismymotivation/ui/login/LoginViewModelTest.kt b/app/src/test/java/com/whereismymotivation/ui/login/LoginViewModelTest.kt index 01f19bf..18316da 100644 --- a/app/src/test/java/com/whereismymotivation/ui/login/LoginViewModelTest.kt +++ b/app/src/test/java/com/whereismymotivation/ui/login/LoginViewModelTest.kt @@ -10,7 +10,6 @@ import com.whereismymotivation.ui.navigation.Destination import com.whereismymotivation.ui.navigation.Navigator import io.mockk.coEvery import io.mockk.coVerify -import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.runs @@ -63,7 +62,7 @@ class LoginViewModelTest { } @Test - fun `should fail if email is invalid`() = runTest { + fun `should fail if email is invalid`(): Unit = runTest { // Data val email = "email" val password = "password" @@ -80,7 +79,7 @@ class LoginViewModelTest { } @Test - fun `should fail if password is less that 6 characters`() = runTest { + fun `should fail if password is less that 6 characters`(): Unit = runTest { // Data val email = "a@b.com" val password = "pass" @@ -97,7 +96,7 @@ class LoginViewModelTest { } @Test - fun `should succeed login with credentials`() = runTest { + fun `should succeed login with credentials`(): Unit = runTest { // Data val auth: Auth = mockk() @@ -110,17 +109,17 @@ class LoginViewModelTest { // Arrange coEvery { authRepository.basicLogin(any(), any()) } returns flow { emit(auth) } - every { userRepository.saveCurrentAuth(any()) } just runs + coEvery { userRepository.saveCurrentAuth(any()) } just runs - every { userRepository.getFirebaseToken() } returns "token" + coEvery { userRepository.getFirebaseToken() } returns "token" - every { userRepository.userExists() } returns true + coEvery { userRepository.userExists() } returns true coEvery { userRepository.sendFirebaseToken(any()) } returns flow { emit("") } - every { userRepository.setFirebaseTokenSent() } just runs + coEvery { userRepository.setFirebaseTokenSent() } just runs - every { userRepository.isOnBoardingComplete() } returns true + coEvery { userRepository.isOnBoardingComplete() } returns true // Act viewModel.basicLogin() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 164c529..9ae4c45 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ room = "2.6.1" hiltKtx = "1.2.0" coil = "2.5.0" lottie = "6.3.0" +datastore = "1.1.1" junit = "4.13.2" mockk = "1.13.9" coroutines-test = "1.7.3" @@ -108,6 +109,9 @@ coil = { module = "io.coil-kt:coil", version.ref = "coil"} coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil"} lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie"} +# Data Store +datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore"} + # Unit Test junit = { module = "junit:junit", version.ref = "junit"} mockk = { module = "io.mockk:mockk", version.ref = "mockk"}