From 2101815f36355394e8e768cc8ecb51a438dbdaa8 Mon Sep 17 00:00:00 2001 From: Stefano Sansone Date: Thu, 25 Apr 2024 14:24:16 +0200 Subject: [PATCH] add following tab property in datastore --- .../omnivore/core/datastore/DataStoreKeys.kt | 18 +++ .../core/datastore/DatastoreRepository.kt | 133 ++++++++++-------- .../omnivore/core/network/Networker.kt | 7 +- .../omnivore/feature/auth/LoginViewModel.kt | 52 ++++--- .../feature/editinfo/EditInfoViewModel.kt | 8 +- .../feature/following/FollowingViewModel.kt | 14 +- .../feature/library/LibraryViewModel.kt | 95 +++++++------ .../feature/profile/ProfileViewModel.kt | 4 +- .../feature/profile/filters/FiltersScreen.kt | 13 +- .../profile/filters/FiltersViewModel.kt | 30 ++++ .../feature/reader/WebReaderViewModel.kt | 36 +++-- .../omnivore/feature/save/SaveViewModel.kt | 11 +- .../app/omnivore/omnivore/utils/Constants.kt | 30 +--- 13 files changed, 258 insertions(+), 193 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DataStoreKeys.kt create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersViewModel.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DataStoreKeys.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DataStoreKeys.kt new file mode 100644 index 000000000..93c01559f --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DataStoreKeys.kt @@ -0,0 +1,18 @@ +package app.omnivore.omnivore.core.datastore + +const val omnivoreSelfHostedApiServer = "omnivoreSelfHostedAPIServer" +const val omnivoreSelfHostedWebServer = "omnivoreSelfHostedWebServer" +const val omnivoreAuthToken = "omnivoreAuthToken" +const val omnivoreAuthCookieString = "omnivoreAuthCookieString" +const val omnivorePendingUserToken = "omnivorePendingUserToken" +const val libraryLastSyncTimestamp = "libraryLastSyncTimestamp" +const val preferredWebFontSize = "preferredWebFontSize" +const val preferredWebLineHeight = "preferredWebLineHeight" +const val preferredWebMaxWidthPercentage = "preferredWebMaxWidthPercentage" +const val preferredWebFontFamily = "preferredWebFontFamily" +const val prefersWebHighContrastText = "prefersWebHighContrastText" +const val prefersJustifyText = "prefersJustifyText" +const val lastUsedSavedItemFilter = "lastUsedSavedItemFilter" +const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter" +const val preferredTheme = "preferredTheme" +const val followingTabActive = "followingTabActive" diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DatastoreRepository.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DatastoreRepository.kt index 1d9a6fa86..c93c8838e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DatastoreRepository.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/datastore/DatastoreRepository.kt @@ -2,79 +2,96 @@ package app.omnivore.omnivore.core.datastore import android.content.Context import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.* +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.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import app.omnivore.omnivore.utils.Constants -import app.omnivore.omnivore.utils.DatastoreKeys import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import javax.inject.Inject interface DatastoreRepository { - val hasAuthTokenFlow: Flow - val themeKeyFlow: Flow + val hasAuthTokenFlow: Flow + val themeKeyFlow: Flow - suspend fun clear() - suspend fun putString(key: String, value: String) - suspend fun putInt(key: String, value: Int) - suspend fun getString(key: String): String? - suspend fun getInt(key: String): Int? - suspend fun clearValue(key: String) + suspend fun clear() + suspend fun putBoolean(key: String, value: Boolean) + fun getBoolean(key: String): Flow + suspend fun putString(key: String, value: String) + suspend fun putInt(key: String, value: Int) + suspend fun getString(key: String): String? + suspend fun getInt(key: String): Int? + suspend fun clearValue(key: String) } class OmnivoreDatastore @Inject constructor( - private val context: Context + private val context: Context ) : DatastoreRepository { - private val Context.dataStore: DataStore by preferencesDataStore( - name = Constants.dataStoreName - ) + private val Context.dataStore: DataStore by preferencesDataStore( + name = Constants.dataStoreName + ) - override suspend fun putString(key: String, value: String) { - val preferencesKey = stringPreferencesKey(key) - context.dataStore.edit { preferences -> - preferences[preferencesKey] = value - } - } - - override suspend fun putInt(key: String, value: Int) { - val preferencesKey = intPreferencesKey(key) - context.dataStore.edit { preferences -> - preferences[preferencesKey] = value - } - } - - override suspend fun getString(key: String): String? { - val preferencesKey = stringPreferencesKey(key) - val preferences = context.dataStore.data.first() - return preferences[preferencesKey] - } - - override suspend fun getInt(key: String): Int? { - val preferencesKey = intPreferencesKey(key) - val preferences = context.dataStore.data.first() - return preferences[preferencesKey] - } - - override suspend fun clear() { - context.dataStore.edit { it.clear() } - } - - override suspend fun clearValue(key: String) { - val preferencesKey = stringPreferencesKey(key) - context.dataStore.edit { it.remove(preferencesKey) } - } - - override val hasAuthTokenFlow: Flow = context - .dataStore.data.map { preferences -> - val key = stringPreferencesKey(DatastoreKeys.omnivoreAuthToken) - val token = preferences[key] - token != null + override suspend fun putBoolean(key: String, value: Boolean) { + val preferencesKey = booleanPreferencesKey(key) + context.dataStore.edit { preferences -> + preferences[preferencesKey] = value + } } - override val themeKeyFlow: Flow = context - .dataStore.data.map { preferences -> - val key = stringPreferencesKey(DatastoreKeys.preferredTheme) - preferences[key] ?: "System" + override fun getBoolean(key: String): Flow { + val preferencesKey = booleanPreferencesKey(key) + return context.dataStore.data.map { preferences -> + preferences[preferencesKey] ?: false + } } + + override suspend fun putString(key: String, value: String) { + val preferencesKey = stringPreferencesKey(key) + context.dataStore.edit { preferences -> + preferences[preferencesKey] = value + } + } + + override suspend fun putInt(key: String, value: Int) { + val preferencesKey = intPreferencesKey(key) + context.dataStore.edit { preferences -> + preferences[preferencesKey] = value + } + } + + override suspend fun getString(key: String): String? { + val preferencesKey = stringPreferencesKey(key) + val preferences = context.dataStore.data.first() + return preferences[preferencesKey] + } + + override suspend fun getInt(key: String): Int? { + val preferencesKey = intPreferencesKey(key) + val preferences = context.dataStore.data.first() + return preferences[preferencesKey] + } + + override suspend fun clear() { + context.dataStore.edit { it.clear() } + } + + override suspend fun clearValue(key: String) { + val preferencesKey = stringPreferencesKey(key) + context.dataStore.edit { it.remove(preferencesKey) } + } + + override val hasAuthTokenFlow: Flow = context.dataStore.data.map { preferences -> + val key = stringPreferencesKey(omnivoreAuthToken) + val token = preferences[key] + token != null + } + + override val themeKeyFlow: Flow = context.dataStore.data.map { preferences -> + val key = stringPreferencesKey(preferredTheme) + preferences[key] ?: "System" + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/Networker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/Networker.kt index 32bdb6e16..4bf66f76d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/Networker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/Networker.kt @@ -1,8 +1,9 @@ package app.omnivore.omnivore.core.network import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.omnivoreAuthToken +import app.omnivore.omnivore.core.datastore.omnivoreSelfHostedApiServer import app.omnivore.omnivore.utils.Constants -import app.omnivore.omnivore.utils.DatastoreKeys import com.apollographql.apollo3.ApolloClient import javax.inject.Inject @@ -10,10 +11,10 @@ class Networker @Inject constructor( private val datastoreRepo: DatastoreRepository ) { suspend fun baseUrl() = - datastoreRepo.getString(DatastoreKeys.omnivoreSelfHostedAPIServer) ?: Constants.apiURL + datastoreRepo.getString(omnivoreSelfHostedApiServer) ?: Constants.apiURL private suspend fun serverUrl() = "${baseUrl()}/api/graphql" - private suspend fun authToken() = datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken) ?: "" + private suspend fun authToken() = datastoreRepo.getString(omnivoreAuthToken) ?: "" suspend fun authenticatedApolloClient() = ApolloClient.Builder().serverUrl(serverUrl()) .addHttpHeader("Authorization", value = authToken()).build() diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/auth/LoginViewModel.kt index 36e4f273a..454b13c7d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/auth/LoginViewModel.kt @@ -5,11 +5,21 @@ import android.widget.Toast import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.* -import app.omnivore.omnivore.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import app.omnivore.omnivore.BuildConfig +import app.omnivore.omnivore.R import app.omnivore.omnivore.core.analytics.EventTracker import app.omnivore.omnivore.core.data.DataService import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.omnivoreAuthCookieString +import app.omnivore.omnivore.core.datastore.omnivoreAuthToken +import app.omnivore.omnivore.core.datastore.omnivorePendingUserToken +import app.omnivore.omnivore.core.datastore.omnivoreSelfHostedApiServer +import app.omnivore.omnivore.core.datastore.omnivoreSelfHostedWebServer import app.omnivore.omnivore.core.network.AuthProviderLoginSubmit import app.omnivore.omnivore.core.network.CreateAccountParams import app.omnivore.omnivore.core.network.CreateAccountSubmit @@ -17,24 +27,26 @@ import app.omnivore.omnivore.core.network.CreateEmailAccountSubmit import app.omnivore.omnivore.core.network.EmailLoginCredentials import app.omnivore.omnivore.core.network.EmailLoginSubmit import app.omnivore.omnivore.core.network.EmailSignUpParams -import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery import app.omnivore.omnivore.core.network.Networker import app.omnivore.omnivore.core.network.PendingUserSubmit import app.omnivore.omnivore.core.network.RetrofitHelper import app.omnivore.omnivore.core.network.SignInParams import app.omnivore.omnivore.core.network.UserProfile import app.omnivore.omnivore.core.network.viewer -import app.omnivore.omnivore.utils.ResourceProvider +import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery import app.omnivore.omnivore.utils.Constants -import app.omnivore.omnivore.utils.DatastoreKeys +import app.omnivore.omnivore.utils.ResourceProvider import com.apollographql.apollo3.ApolloClient import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException import com.google.android.gms.tasks.Task import dagger.hilt.android.lifecycle.HiltViewModel import io.intercom.android.sdk.Intercom -import kotlinx.coroutines.* +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.util.regex.Pattern import javax.inject.Inject @@ -84,13 +96,13 @@ class LoginViewModel @Inject constructor( val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) fun getAuthCookieString(): String? = runBlocking { - datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) + datastoreRepo.getString(omnivoreAuthCookieString) } fun setSelfHostingDetails(context: Context, apiServer: String, webServer: String) { viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.omnivoreSelfHostedAPIServer, apiServer) - datastoreRepo.putString(DatastoreKeys.omnivoreSelfHostedWebServer, webServer) + datastoreRepo.putString(omnivoreSelfHostedApiServer, apiServer) + datastoreRepo.putString(omnivoreSelfHostedWebServer, webServer) Toast.makeText( context, context.getString(R.string.login_view_model_self_hosting_settings_updated), @@ -101,8 +113,8 @@ class LoginViewModel @Inject constructor( fun resetSelfHostingDetails(context: Context) { viewModelScope.launch { - datastoreRepo.clearValue(DatastoreKeys.omnivoreSelfHostedAPIServer) - datastoreRepo.clearValue(DatastoreKeys.omnivoreSelfHostedWebServer) + datastoreRepo.clearValue(omnivoreSelfHostedApiServer) + datastoreRepo.clearValue(omnivoreSelfHostedWebServer) Toast.makeText( context, context.getString(R.string.login_view_model_self_hosting_settings_reset), @@ -136,7 +148,7 @@ class LoginViewModel @Inject constructor( fun cancelNewUserSignUp() { resetState() viewModelScope.launch { - datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) + datastoreRepo.clearValue(omnivorePendingUserToken) } showSocialLogin() } @@ -235,7 +247,7 @@ class LoginViewModel @Inject constructor( } if (result.body()?.authToken != null) { - datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) + datastoreRepo.putString(omnivoreAuthToken, result.body()?.authToken!!) } else { errorMessage = resourceProvider.getString( R.string.login_view_model_something_went_wrong_error_msg) @@ -243,7 +255,7 @@ class LoginViewModel @Inject constructor( if (result.body()?.authCookieString != null) { datastoreRepo.putString( - DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! + omnivoreAuthCookieString, result.body()?.authCookieString!! ) } } @@ -282,7 +294,7 @@ class LoginViewModel @Inject constructor( } private fun getPendingAuthToken(): String? = runBlocking { - datastoreRepo.getString(DatastoreKeys.omnivorePendingUserToken) + datastoreRepo.getString(omnivorePendingUserToken) } fun submitProfile(username: String, name: String) { @@ -305,7 +317,7 @@ class LoginViewModel @Inject constructor( isLoading = false if (result.body()?.authToken != null) { - datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) + datastoreRepo.putString(omnivoreAuthToken, result.body()?.authToken!!) } else { errorMessage = resourceProvider.getString( R.string.login_view_model_something_went_wrong_error_msg) @@ -313,7 +325,7 @@ class LoginViewModel @Inject constructor( if (result.body()?.authCookieString != null) { datastoreRepo.putString( - DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! + omnivoreAuthCookieString, result.body()?.authCookieString!! ) } } @@ -371,11 +383,11 @@ class LoginViewModel @Inject constructor( isLoading = false if (result.body()?.authToken != null) { - datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) + datastoreRepo.putString(omnivoreAuthToken, result.body()?.authToken!!) if (result.body()?.authCookieString != null) { datastoreRepo.putString( - DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! + omnivoreAuthCookieString, result.body()?.authCookieString!! ) } } else { @@ -409,7 +421,7 @@ class LoginViewModel @Inject constructor( if (result.body()?.pendingUserToken != null) { datastoreRepo.putString( - DatastoreKeys.omnivorePendingUserToken, result.body()?.pendingUserToken!! + omnivorePendingUserToken, result.body()?.pendingUserToken!! ) registrationStateLiveData.value = RegistrationState.PendingUser } else { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/editinfo/EditInfoViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/editinfo/EditInfoViewModel.kt index 2d4197eca..bcd64b9fc 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/editinfo/EditInfoViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/editinfo/EditInfoViewModel.kt @@ -6,13 +6,13 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.omnivore.omnivore.utils.Constants -import app.omnivore.omnivore.utils.DatastoreKeys -import app.omnivore.omnivore.core.datastore.DatastoreRepository import app.omnivore.omnivore.R import app.omnivore.omnivore.core.data.DataService +import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.omnivoreAuthToken import app.omnivore.omnivore.graphql.generated.UpdatePageMutation import app.omnivore.omnivore.graphql.generated.type.UpdatePageInput +import app.omnivore.omnivore.utils.Constants import app.omnivore.omnivore.utils.ResourceProvider import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional @@ -45,7 +45,7 @@ class EditInfoViewModel @Inject constructor( private set private fun getAuthToken(): String? = runBlocking { - datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken) + datastoreRepo.getString(omnivoreAuthToken) } fun editInfo(itemId: String, title: String, author: String?, description: String?) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt index 145a3a99d..dbd7d2280 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt @@ -13,12 +13,14 @@ import app.omnivore.omnivore.core.data.repository.LibraryRepository import app.omnivore.omnivore.core.database.entities.SavedItemLabel import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.lastUsedSavedItemFilter +import app.omnivore.omnivore.core.datastore.lastUsedSavedItemSortFilter +import app.omnivore.omnivore.core.datastore.libraryLastSyncTimestamp import app.omnivore.omnivore.feature.library.LibraryBottomSheetState import app.omnivore.omnivore.feature.library.SavedItemAction import app.omnivore.omnivore.feature.library.SavedItemFilter import app.omnivore.omnivore.feature.library.SavedItemSortFilter import app.omnivore.omnivore.feature.library.SavedItemViewModel -import app.omnivore.omnivore.utils.DatastoreKeys import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -118,7 +120,7 @@ class FollowingViewModel @Inject constructor( } private fun getLastSyncTime(): Instant? = runBlocking { - datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let { + datastoreRepo.getString(libraryLastSyncTimestamp)?.let { try { return@let Instant.parse(it) } catch (e: Exception) { @@ -164,7 +166,7 @@ class FollowingViewModel @Inject constructor( fun updateSavedItemFilter(filter: SavedItemFilter) { viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue) + datastoreRepo.putString(lastUsedSavedItemFilter, filter.rawValue) appliedFilterState.value = filter handleFilterChanges() } @@ -172,7 +174,7 @@ class FollowingViewModel @Inject constructor( fun updateSavedItemSortFilter(filter: SavedItemSortFilter) { viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemSortFilter, filter.rawValue) + datastoreRepo.putString(lastUsedSavedItemSortFilter, filter.rawValue) appliedSortFilterLiveData.value = filter handleFilterChanges() } @@ -225,7 +227,7 @@ class FollowingViewModel @Inject constructor( _libraryQuery.value = LibraryQuery( allowedArchiveStates = allowedArchiveStates, sortKey = sortKey, - requiredLabels = requiredLabels + listOf("Newsletter", "RSS"), + requiredLabels = requiredLabels, excludedLabels = excludeLabels, allowedContentReaders = allowedContentReaders ) @@ -274,7 +276,7 @@ class FollowingViewModel @Inject constructor( isInitialBatch = false ) } else { - datastoreRepo.putString(DatastoreKeys.libraryLastSyncTimestamp, startTime) + datastoreRepo.putString(libraryLastSyncTimestamp, startTime) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt index b4d4d1198..7345c41cd 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt @@ -13,7 +13,9 @@ import app.omnivore.omnivore.core.data.repository.LibraryRepository import app.omnivore.omnivore.core.database.entities.SavedItemLabel import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.core.datastore.DatastoreRepository -import app.omnivore.omnivore.utils.DatastoreKeys +import app.omnivore.omnivore.core.datastore.lastUsedSavedItemFilter +import app.omnivore.omnivore.core.datastore.lastUsedSavedItemSortFilter +import app.omnivore.omnivore.core.datastore.libraryLastSyncTimestamp import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -112,7 +114,7 @@ class LibraryViewModel @Inject constructor( } private fun getLastSyncTime(): Instant? = runBlocking { - datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let { + datastoreRepo.getString(libraryLastSyncTimestamp)?.let { try { return@let Instant.parse(it) } catch (e: Exception) { @@ -158,7 +160,7 @@ class LibraryViewModel @Inject constructor( fun updateSavedItemFilter(filter: SavedItemFilter) { viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue) + datastoreRepo.putString(lastUsedSavedItemFilter, filter.rawValue) appliedFilterState.value = filter handleFilterChanges() } @@ -166,7 +168,7 @@ class LibraryViewModel @Inject constructor( fun updateSavedItemSortFilter(filter: SavedItemSortFilter) { viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemSortFilter, filter.rawValue) + datastoreRepo.putString(lastUsedSavedItemSortFilter, filter.rawValue) appliedSortFilterLiveData.value = filter handleFilterChanges() } @@ -182,49 +184,46 @@ class LibraryViewModel @Inject constructor( private fun handleFilterChanges() { librarySearchCursor = null - if (appliedSortFilterLiveData.value != null && appliedFilterState.value != null) { - val sortKey = when (appliedSortFilterLiveData.value) { - SavedItemSortFilter.NEWEST -> "newest" - SavedItemSortFilter.OLDEST -> "oldest" - SavedItemSortFilter.RECENTLY_READ -> "recentlyRead" - SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished" - else -> "newest" - } - - val allowedArchiveStates = when (appliedFilterState.value) { - SavedItemFilter.ALL -> listOf(0, 1) - SavedItemFilter.ARCHIVED -> listOf(1) - else -> listOf(0) - } - - val allowedContentReaders = when (appliedFilterState.value) { - SavedItemFilter.FILES -> listOf("PDF", "EPUB") - else -> listOf("WEB", "PDF", "EPUB") - } - - var requiredLabels = when (appliedFilterState.value) { - SavedItemFilter.NEWSLETTERS -> listOf("Newsletter") - SavedItemFilter.FEEDS -> listOf("RSS") - else -> activeLabels.value.map { it.name } - } - - activeLabels.value.let { it -> - requiredLabels = requiredLabels + it.map { it.name } - } - - val excludeLabels = when (appliedFilterState.value) { - SavedItemFilter.NON_FEED -> listOf("Newsletter", "RSS") - else -> listOf("Newsletter", "RSS") - } - - _libraryQuery.value = LibraryQuery( - allowedArchiveStates = allowedArchiveStates, - sortKey = sortKey, - requiredLabels = requiredLabels, - excludedLabels = excludeLabels, - allowedContentReaders = allowedContentReaders - ) + val sortKey = when (appliedSortFilterLiveData.value) { + SavedItemSortFilter.NEWEST -> "newest" + SavedItemSortFilter.OLDEST -> "oldest" + SavedItemSortFilter.RECENTLY_READ -> "recentlyRead" + SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished" } + + val allowedArchiveStates = when (appliedFilterState.value) { + SavedItemFilter.ALL -> listOf(0, 1) + SavedItemFilter.ARCHIVED -> listOf(1) + else -> listOf(0) + } + + val allowedContentReaders = when (appliedFilterState.value) { + SavedItemFilter.FILES -> listOf("PDF", "EPUB") + else -> listOf("WEB", "PDF", "EPUB") + } + + var requiredLabels = when (appliedFilterState.value) { + SavedItemFilter.NEWSLETTERS -> listOf("Newsletter") + SavedItemFilter.FEEDS -> listOf("RSS") + else -> activeLabels.value.map { it.name } + } + + activeLabels.value.let { it -> + requiredLabels = requiredLabels + it.map { it.name } + } + + val excludeLabels = when (appliedFilterState.value) { + SavedItemFilter.NON_FEED -> listOf("Newsletter", "RSS") + else -> listOf("Newsletter", "RSS") + } + + _libraryQuery.value = LibraryQuery( + allowedArchiveStates = allowedArchiveStates, + sortKey = sortKey, + requiredLabels = requiredLabels, + excludedLabels = excludeLabels, + allowedContentReaders = allowedContentReaders + ) } private suspend fun syncItems() { @@ -270,7 +269,7 @@ class LibraryViewModel @Inject constructor( isInitialBatch = false ) } else { - datastoreRepo.putString(DatastoreKeys.libraryLastSyncTimestamp, startTime) + datastoreRepo.putString(libraryLastSyncTimestamp, startTime) } } @@ -360,7 +359,7 @@ class LibraryViewModel @Inject constructor( private fun searchQueryString(): String { var query = - "${appliedFilterState.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}" + "${appliedFilterState.value.queryString} ${appliedSortFilterLiveData.value.queryString}" activeLabels.value.let { if (it.isNotEmpty()) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/ProfileViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/ProfileViewModel.kt index afa009399..10dce3605 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/ProfileViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/ProfileViewModel.kt @@ -7,9 +7,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.core.data.DataService import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.libraryLastSyncTimestamp import app.omnivore.omnivore.core.network.Networker import app.omnivore.omnivore.core.network.viewer -import app.omnivore.omnivore.utils.DatastoreKeys import dagger.hilt.android.lifecycle.HiltViewModel import io.intercom.android.sdk.Intercom import io.intercom.android.sdk.IntercomSpace @@ -33,7 +33,7 @@ class ProfileViewModel @Inject constructor( isResettingData = true viewModelScope.launch { - datastoreRepo.clearValue(DatastoreKeys.libraryLastSyncTimestamp) + datastoreRepo.clearValue(libraryLastSyncTimestamp) dataService.clearDatabase() delay(1000) isResettingData = false diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersScreen.kt index 6dfcdd456..70459c3e6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersScreen.kt @@ -13,21 +13,18 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import app.omnivore.omnivore.R import app.omnivore.omnivore.core.designsystem.component.SwitchPreferenceWidget -import app.omnivore.omnivore.feature.profile.ProfileViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun FiltersScreen( navController: NavHostController, - settingsViewModel: ProfileViewModel = hiltViewModel() + filtersViewModel: FiltersViewModel = hiltViewModel() ) { Scaffold( @@ -52,12 +49,12 @@ internal fun FiltersScreen( contentPadding = contentPadding, ) { item { - var checked by remember { mutableStateOf(true) } + val followingTabActive by filtersViewModel.followingTabActiveState.collectAsStateWithLifecycle() SwitchPreferenceWidget( title = stringResource(R.string.hide_following_tab), - checked = checked, - onCheckedChanged = { checked = it }, + checked = followingTabActive, + onCheckedChanged = { filtersViewModel.setFollowingTabActiveState(it) }, ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersViewModel.kt new file mode 100644 index 000000000..93f6ea569 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/filters/FiltersViewModel.kt @@ -0,0 +1,30 @@ +package app.omnivore.omnivore.feature.profile.filters + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.followingTabActive +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FiltersViewModel @Inject constructor( + private val datastoreRepository: DatastoreRepository +) : ViewModel() { + + val followingTabActiveState: StateFlow = datastoreRepository.getBoolean(followingTabActive).stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false + ) + + fun setFollowingTabActiveState(value: Boolean) { + viewModelScope.launch { + datastoreRepository.putBoolean(followingTabActive, value) + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt index 60ce7e117..3d85d3069 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt @@ -28,6 +28,13 @@ import app.omnivore.omnivore.core.database.dao.SavedItemDao import app.omnivore.omnivore.core.database.entities.SavedItem import app.omnivore.omnivore.core.database.entities.SavedItemLabel import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.preferredTheme +import app.omnivore.omnivore.core.datastore.preferredWebFontFamily +import app.omnivore.omnivore.core.datastore.preferredWebFontSize +import app.omnivore.omnivore.core.datastore.preferredWebLineHeight +import app.omnivore.omnivore.core.datastore.preferredWebMaxWidthPercentage +import app.omnivore.omnivore.core.datastore.prefersJustifyText +import app.omnivore.omnivore.core.datastore.prefersWebHighContrastText import app.omnivore.omnivore.core.network.Networker import app.omnivore.omnivore.core.network.createNewLabel import app.omnivore.omnivore.core.network.saveUrl @@ -35,7 +42,6 @@ import app.omnivore.omnivore.core.network.savedItem import app.omnivore.omnivore.feature.components.HighlightColor import app.omnivore.omnivore.feature.library.SavedItemAction import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput -import app.omnivore.omnivore.utils.DatastoreKeys import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel @@ -454,21 +460,21 @@ class WebReaderViewModel @Inject constructor( .asLiveData() fun storedWebPreferences(isDarkMode: Boolean): WebPreferences = runBlocking { - val storedFontSize = datastoreRepo.getInt(DatastoreKeys.preferredWebFontSize) - val storedLineHeight = datastoreRepo.getInt(DatastoreKeys.preferredWebLineHeight) - val storedMaxWidth = datastoreRepo.getInt(DatastoreKeys.preferredWebMaxWidthPercentage) + val storedFontSize = datastoreRepo.getInt(preferredWebFontSize) + val storedLineHeight = datastoreRepo.getInt(preferredWebLineHeight) + val storedMaxWidth = datastoreRepo.getInt(preferredWebMaxWidthPercentage) val storedFontFamily = - datastoreRepo.getString(DatastoreKeys.preferredWebFontFamily) ?: WebFont.SYSTEM.rawValue + datastoreRepo.getString(preferredWebFontFamily) ?: WebFont.SYSTEM.rawValue val storedThemePreference = - datastoreRepo.getString(DatastoreKeys.preferredTheme) ?: "System" + datastoreRepo.getString(preferredTheme) ?: "System" val storedWebFont = WebFont.entries.firstOrNull { it.rawValue == storedFontFamily } ?: WebFont.entries .first() val prefersHighContrastFont = - datastoreRepo.getString(DatastoreKeys.prefersWebHighContrastText) == "true" - val prefersJustifyText = datastoreRepo.getString(DatastoreKeys.prefersJustifyText) == "true" + datastoreRepo.getString(prefersWebHighContrastText) == "true" + val prefersJustifyText = datastoreRepo.getString(prefersJustifyText) == "true" WebPreferences( textFontSize = storedFontSize ?: 12, @@ -494,7 +500,7 @@ class WebReaderViewModel @Inject constructor( Log.d("theme", "Setting theme key: $newThemeKey") runBlocking { - datastoreRepo.putString(DatastoreKeys.preferredTheme, newThemeKey) + datastoreRepo.putString(preferredTheme, newThemeKey) } val script = @@ -504,7 +510,7 @@ class WebReaderViewModel @Inject constructor( fun setFontSize(newFontSize: Int) { runBlocking { - datastoreRepo.putInt(DatastoreKeys.preferredWebFontSize, newFontSize) + datastoreRepo.putInt(preferredWebFontSize, newFontSize) } val script = "var event = new Event('updateFontSize');event.fontSize = '$newFontSize';document.dispatchEvent(event);" @@ -514,7 +520,7 @@ class WebReaderViewModel @Inject constructor( fun setMaxWidthPercentage(newMaxWidthPercentageValue: Int) { runBlocking { datastoreRepo.putInt( - DatastoreKeys.preferredWebMaxWidthPercentage, + preferredWebMaxWidthPercentage, newMaxWidthPercentageValue ) } @@ -525,7 +531,7 @@ class WebReaderViewModel @Inject constructor( fun setLineHeight(newLineHeight: Int) { runBlocking { - datastoreRepo.putInt(DatastoreKeys.preferredWebLineHeight, newLineHeight) + datastoreRepo.putInt(preferredWebLineHeight, newLineHeight) } val script = "var event = new Event('updateLineHeight');event.lineHeight = '$newLineHeight';document.dispatchEvent(event);" @@ -535,7 +541,7 @@ class WebReaderViewModel @Inject constructor( fun updateHighContrastTextPreference(prefersHighContrastText: Boolean) { runBlocking { datastoreRepo.putString( - DatastoreKeys.prefersWebHighContrastText, + prefersWebHighContrastText, prefersHighContrastText.toString() ) } @@ -547,7 +553,7 @@ class WebReaderViewModel @Inject constructor( fun updateJustifyText(justifyText: Boolean) { runBlocking { - datastoreRepo.putString(DatastoreKeys.prefersJustifyText, justifyText.toString()) + datastoreRepo.putString(prefersJustifyText, justifyText.toString()) } val script = "var event = new Event('updateJustifyText');event.justifyText = $justifyText;document.dispatchEvent(event);" @@ -556,7 +562,7 @@ class WebReaderViewModel @Inject constructor( fun applyWebFont(font: WebFont) { runBlocking { - datastoreRepo.putString(DatastoreKeys.preferredWebFontFamily, font.rawValue) + datastoreRepo.putString(preferredWebFontFamily, font.rawValue) } val script = diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveViewModel.kt index 9fbf64228..b708c8b04 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/save/SaveViewModel.kt @@ -8,19 +8,20 @@ import androidx.compose.ui.text.intl.Locale import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.omnivore.omnivore.utils.Constants -import app.omnivore.omnivore.utils.DatastoreKeys -import app.omnivore.omnivore.core.datastore.DatastoreRepository import app.omnivore.omnivore.R +import app.omnivore.omnivore.core.datastore.DatastoreRepository +import app.omnivore.omnivore.core.datastore.omnivoreAuthToken import app.omnivore.omnivore.graphql.generated.SaveUrlMutation import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput +import app.omnivore.omnivore.utils.Constants import app.omnivore.omnivore.utils.ResourceProvider import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import java.util.* +import java.util.TimeZone +import java.util.UUID import java.util.regex.Pattern import javax.inject.Inject @@ -48,7 +49,7 @@ class SaveViewModel @Inject constructor( private set private fun getAuthToken(): String? = runBlocking { - datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken) + datastoreRepo.getString(omnivoreAuthToken) } /** diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/utils/Constants.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/utils/Constants.kt index b4962979a..90e23d8e1 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/utils/Constants.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/utils/Constants.kt @@ -3,31 +3,13 @@ package app.omnivore.omnivore.utils import app.omnivore.omnivore.BuildConfig object Constants { - const val apiURL = BuildConfig.OMNIVORE_API_URL - const val dataStoreName = "omnivore-datastore" -} - -object DatastoreKeys { - const val omnivoreSelfHostedAPIServer = "omnivoreSelfHostedAPIServer" - const val omnivoreSelfHostedWebServer = "omnivoreSelfHostedWebServer" - const val omnivoreAuthToken = "omnivoreAuthToken" - const val omnivoreAuthCookieString = "omnivoreAuthCookieString" - const val omnivorePendingUserToken = "omnivorePendingUserToken" - const val libraryLastSyncTimestamp = "libraryLastSyncTimestamp" - const val preferredWebFontSize = "preferredWebFontSize" - const val preferredWebLineHeight = "preferredWebLineHeight" - const val preferredWebMaxWidthPercentage = "preferredWebMaxWidthPercentage" - const val preferredWebFontFamily = "preferredWebFontFamily" - const val prefersWebHighContrastText = "prefersWebHighContrastText" - const val prefersJustifyText = "prefersJustifyText" - const val lastUsedSavedItemFilter = "lastUsedSavedItemFilter" - const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter" - const val preferredTheme = "preferredTheme" + const val apiURL = BuildConfig.OMNIVORE_API_URL + const val dataStoreName = "omnivore-datastore" } object AppleConstants { - const val clientId = "app.omnivore" - const val redirectURI = BuildConfig.OMNIVORE_API_URL + "/api/mobile-auth/android-apple-redirect" - const val scope = "name%20email" - const val authUrl = "https://appleid.apple.com/auth/authorize" + const val clientId = "app.omnivore" + const val redirectURI = BuildConfig.OMNIVORE_API_URL + "/api/mobile-auth/android-apple-redirect" + const val scope = "name%20email" + const val authUrl = "https://appleid.apple.com/auth/authorize" }