Przeglądaj źródła

Merge pull request #3249 from nextcloud/issue-3043-migrate-from-storebox

Migrating away from StoreBox to DataStore
Marcel Hibbe 1 rok temu
rodzic
commit
510bc8e694

+ 2 - 1
app/build.gradle

@@ -164,6 +164,8 @@ configurations.all {
 
 dependencies {
     implementation 'androidx.preference:preference-ktx:1.2.1'
+    implementation 'androidx.datastore:datastore-core:1.0.0'
+    implementation 'androidx.datastore:datastore-preferences:1.0.0'
     detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1")
 	
     implementation fileTree(include: ['*'], dir: 'libs')
@@ -237,7 +239,6 @@ dependencies {
     implementation "androidx.room:room-ktx:${roomVersion}"
 
     implementation "org.parceler:parceler-api:$parcelerVersion"
-    implementation 'net.orange-box.storebox:storebox-lib:1.4.0'
     implementation 'eu.davidea:flexible-adapter:5.1.0'
     implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
     implementation 'org.apache.commons:commons-lang3:3.13.0'

+ 11 - 3
app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java

@@ -25,25 +25,33 @@ import android.content.Context;
 
 import com.nextcloud.talk.data.source.local.TalkDatabase;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
-
-import net.orange_box.storebox.StoreBox;
+import com.nextcloud.talk.utils.preferences.AppPreferencesImpl;
 
 import javax.inject.Singleton;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
 import dagger.Module;
 import dagger.Provides;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 @Module
+@OptIn(markerClass = ExperimentalCoroutinesApi.class)
 public class DatabaseModule {
     @Provides
     @Singleton
     public AppPreferences providePreferences(@NonNull final Context poContext) {
-        AppPreferences preferences = StoreBox.create(poContext, AppPreferences.class);
+        AppPreferences preferences = new AppPreferencesImpl(poContext);
         preferences.removeLinkPreviews();
         return preferences;
     }
 
+    @Provides
+    @Singleton
+    public AppPreferencesImpl providePreferencesImpl(@NonNull final Context poContext) {
+        return new AppPreferencesImpl(poContext);
+    }
+
     @Provides
     @Singleton
     public TalkDatabase provideTalkDatabase(@NonNull final Context context,

+ 25 - 16
app/src/main/java/com/nextcloud/talk/remotefilebrowser/viewmodels/RemoteFileBrowserItemsViewModel.kt

@@ -22,20 +22,26 @@
 
 package com.nextcloud.talk.remotefilebrowser.viewmodels
 
+import android.content.res.Resources
 import android.util.Log
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import com.nextcloud.talk.R
 import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
 import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
 import com.nextcloud.talk.utils.FileSortOrder
 import com.nextcloud.talk.utils.Mimetype.FOLDER
-import com.nextcloud.talk.utils.preferences.AppPreferences
+import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
-import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
 import java.io.File
 import javax.inject.Inject
 
@@ -52,9 +58,12 @@ import javax.inject.Inject
  * FinishState --> [*]
  * @enduml
  */
-class RemoteFileBrowserItemsViewModel @Inject constructor(
+@OptIn(ExperimentalCoroutinesApi::class)
+class RemoteFileBrowserItemsViewModel
+@Inject
+constructor(
     private val repository: RemoteFileBrowserItemsRepository,
-    private val appPreferences: AppPreferences
+    private val appPreferences: AppPreferencesImpl
 ) :
     ViewModel() {
 
@@ -66,7 +75,8 @@ class RemoteFileBrowserItemsViewModel @Inject constructor(
     class FinishState(val selectedPaths: Set<String>) : ViewState
 
     private val initialSortOrder = FileSortOrder.getFileSortOrder(appPreferences.sorting)
-    private val sortingPrefListener: SortChangeListener = SortChangeListener()
+
+    private var sortingFlow: Flow<String>
 
     private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
     val viewState: LiveData<ViewState>
@@ -86,20 +96,19 @@ class RemoteFileBrowserItemsViewModel @Inject constructor(
         get() = _selectedPaths
 
     init {
-        appPreferences.registerSortingChangeListener(sortingPrefListener)
-    }
-
-    inner class SortChangeListener : OnPreferenceValueChangedListener<String> {
-        override fun onChanged(newValue: String) {
-            onSelectSortOrder(newValue)
+        val key = Resources.getSystem().getString(R.string.nc_file_browser_sort_by_key)
+        sortingFlow = appPreferences.readString(key)
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.sorting
+            sortingFlow.collect { newString ->
+                if (newString != state) {
+                    state = newString
+                    onSelectSortOrder(newString)
+                }
+            }
         }
     }
 
-    override fun onCleared() {
-        super.onCleared()
-        appPreferences.unregisterSortingChangeListener(sortingPrefListener)
-    }
-
     fun loadItems() {
         _viewState.value = LoadingItemsState
         repository.listFolder(currentPath.value!!).subscribeOn(Schedulers.io())

+ 205 - 178
app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt

@@ -27,7 +27,6 @@ package com.nextcloud.talk.settings
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.app.Activity
 import android.app.KeyguardManager
 import android.content.Context
 import android.content.DialogInterface
@@ -90,12 +89,17 @@ import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
 import com.nextcloud.talk.utils.SecurityUtils
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
+import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
-import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.RequestBody.Companion.toRequestBody
 import java.net.URI
@@ -119,15 +123,15 @@ class SettingsActivity : BaseActivity() {
 
     private var currentUser: User? = null
     private var credentials: String? = null
-    private var proxyTypeChangeListener: OnPreferenceValueChangedListener<String>? = null
-    private var proxyCredentialsChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
-    private var screenSecurityChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
-    private var screenLockChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
-    private var screenLockTimeoutChangeListener: OnPreferenceValueChangedListener<String?>? = null
-    private var themeChangeListener: OnPreferenceValueChangedListener<String?>? = null
-    private var readPrivacyChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
-    private var typingStatusChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
-    private var phoneBookIntegrationChangeListener: OnPreferenceValueChangedListener<Boolean>? = null
+    private lateinit var proxyTypeFlow: Flow<String>
+    private lateinit var proxyCredentialFlow: Flow<Boolean>
+    private lateinit var screenSecurityFlow: Flow<Boolean>
+    private lateinit var screenLockFlow: Flow<Boolean>
+    private lateinit var screenLockTimeoutFlow: Flow<String>
+    private lateinit var themeFlow: Flow<String>
+    private lateinit var readPrivacyFlow: Flow<Boolean>
+    private lateinit var typingStatusFlow: Flow<Boolean>
+    private lateinit var phoneBookIntegrationFlow: Flow<Boolean>
     private var profileQueryDisposable: Disposable? = null
     private var dbQueryDisposable: Disposable? = null
 
@@ -144,7 +148,6 @@ class SettingsActivity : BaseActivity() {
 
         getCurrentUser()
 
-        // setupSettingsScreen()
         setupLicenceSetting()
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@@ -381,59 +384,53 @@ class SettingsActivity : BaseActivity() {
         }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     private fun registerChangeListeners() {
-        appPreferences.registerProxyTypeListener(ProxyTypeChangeListener().also { proxyTypeChangeListener = it })
-        appPreferences.registerProxyCredentialsListener(
-            ProxyCredentialsChangeListener().also {
-                proxyCredentialsChangeListener = it
-            }
-        )
-        appPreferences.registerScreenSecurityListener(
-            ScreenSecurityChangeListener().also {
-                screenSecurityChangeListener = it
-            }
-        )
+        val appPreferences = AppPreferencesImpl(context)
+        proxyTypeFlow = appPreferences.readString(AppPreferencesImpl.PROXY_TYPE)
+        proxyCredentialFlow = appPreferences.readBoolean(AppPreferencesImpl.PROXY_CRED)
+        screenSecurityFlow = appPreferences.readBoolean(AppPreferencesImpl.SCREEN_SECURITY)
+        screenLockFlow = appPreferences.readBoolean(AppPreferencesImpl.SCREEN_LOCK)
+        screenLockTimeoutFlow = appPreferences.readString(AppPreferencesImpl.SCREEN_LOCK_TIMEOUT)
+
+        val themeKey = context.resources.getString(R.string.nc_settings_theme_key)
+        themeFlow = appPreferences.readString(themeKey)
+
+        val privacyKey = context.resources.getString(R.string.nc_settings_read_privacy_key)
+        readPrivacyFlow = appPreferences.readBoolean(privacyKey)
+
+        typingStatusFlow = appPreferences.readBoolean(AppPreferencesImpl.TYPING_STATUS)
+        phoneBookIntegrationFlow = appPreferences.readBoolean(AppPreferencesImpl.PHONE_BOOK_INTEGRATION)
+
         var pos = resources.getStringArray(R.array.screen_lock_timeout_entry_values).indexOf(
             appPreferences.screenLockTimeout
         )
         binding.settingsScreenLockTimeoutLayoutDropdown.setText(
             resources.getStringArray(R.array.screen_lock_timeout_descriptions)[pos]
         )
+
         binding.settingsScreenLockTimeoutLayoutDropdown.setSimpleItems(R.array.screen_lock_timeout_descriptions)
         binding.settingsScreenLockTimeoutLayoutDropdown.setOnItemClickListener { _, _, position, _ ->
             val entryVal: String = resources.getStringArray(R.array.screen_lock_timeout_entry_values)[position]
             appPreferences.screenLockTimeout = entryVal
+            SecurityUtils.createKey(entryVal)
         }
-        appPreferences.registerScreenLockListener(ScreenLockListener().also { screenLockChangeListener = it })
-        appPreferences.registerScreenLockTimeoutListener(
-            ScreenLockTimeoutListener().also {
-                screenLockTimeoutChangeListener = it
-            }
-        )
         pos = resources.getStringArray(R.array.theme_entry_values).indexOf(appPreferences.theme)
         binding.settingsTheme.setText(resources.getStringArray(R.array.theme_descriptions)[pos])
+
         binding.settingsTheme.setSimpleItems(R.array.theme_descriptions)
         binding.settingsTheme.setOnItemClickListener { _, _, position, _ ->
             val entryVal: String = resources.getStringArray(R.array.theme_entry_values)[position]
             appPreferences.theme = entryVal
         }
-        appPreferences.registerThemeChangeListener(ThemeChangeListener().also { themeChangeListener = it })
-        appPreferences.registerPhoneBookIntegrationChangeListener(
-            PhoneBookIntegrationChangeListener(this).also {
-                phoneBookIntegrationChangeListener = it
-            }
-        )
-        appPreferences.registerReadPrivacyChangeListener(
-            ReadPrivacyChangeListener().also {
-                readPrivacyChangeListener = it
-            }
-        )
 
-        appPreferences.registerTypingStatusChangeListener(
-            TypingStatusChangeListener().also {
-                typingStatusChangeListener = it
-            }
-        )
+        observeProxyType()
+        observeProxyCredential()
+        observeScreenSecurity()
+        observeScreenLock()
+        observeTheme()
+        observeReadPrivacy()
+        observeTypingStatus()
     }
 
     fun sendLogs() {
@@ -694,10 +691,10 @@ class SettingsActivity : BaseActivity() {
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             binding.settingsIncognitoKeyboardSwitch.isChecked = appPreferences.isKeyboardIncognito
+        } else {
+            binding.settingsIncognitoKeyboardSwitch.visibility = View.GONE
         }
 
-        binding.settingsIncognitoKeyboardSwitch.isChecked = appPreferences.isKeyboardIncognito
-
         if (CapabilitiesUtilNew.isReadStatusAvailable(currentUser!!)) {
             binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtilNew.isReadStatusPrivate(currentUser!!)
         } else {
@@ -738,6 +735,13 @@ class SettingsActivity : BaseActivity() {
             val isChecked = binding.settingsPhoneBookIntegrationSwitch.isChecked
             binding.settingsPhoneBookIntegrationSwitch.isChecked = !isChecked
             appPreferences.setPhoneBookIntegration(!isChecked)
+            if (!isChecked) {
+                if (checkPermission(this@SettingsActivity, (context))) {
+                    checkForPhoneNumber()
+                }
+            } else {
+                deleteAll()
+            }
         }
 
         binding.settingsScreenSecurity.setOnClickListener {
@@ -798,15 +802,15 @@ class SettingsActivity : BaseActivity() {
     }
 
     public override fun onDestroy() {
-        appPreferences.unregisterProxyTypeListener(proxyTypeChangeListener)
-        appPreferences.unregisterProxyCredentialsListener(proxyCredentialsChangeListener)
-        appPreferences.unregisterScreenSecurityListener(screenSecurityChangeListener)
-        appPreferences.unregisterScreenLockListener(screenLockChangeListener)
-        appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener)
-        appPreferences.unregisterThemeChangeListener(themeChangeListener)
-        appPreferences.unregisterReadPrivacyChangeListener(readPrivacyChangeListener)
-        appPreferences.unregisterTypingStatusChangeListener(typingStatusChangeListener)
-        appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener)
+        // appPreferences.unregisterProxyTypeListener(proxyTypeChangeListener)
+        // appPreferences.unregisterProxyCredentialsListener(proxyCredentialsChangeListener)
+        // appPreferences.unregisterScreenSecurityListener(screenSecurityChangeListener)
+        // appPreferences.unregisterScreenLockListener(screenLockChangeListener)
+        // appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener)
+        // appPreferences.unregisterThemeChangeListener(themeChangeListener)
+        // appPreferences.unregisterReadPrivacyChangeListener(readPrivacyChangeListener)
+        // appPreferences.unregisterTypingStatusChangeListener(typingStatusChangeListener)
+        // appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener)
 
         super.onDestroy()
     }
@@ -819,8 +823,7 @@ class SettingsActivity : BaseActivity() {
         appPreferences.removeProxyPassword()
         binding.settingsProxyHostLayout.visibility = View.GONE
         binding.settingsProxyPortLayout.visibility = View.GONE
-        binding.settingsProxyUseCredentials.visibility =
-            View.GONE
+        binding.settingsProxyUseCredentials.visibility = View.GONE
         hideProxyCredentials()
     }
 
@@ -896,86 +899,98 @@ class SettingsActivity : BaseActivity() {
         }
     }
 
-    private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener<String?> {
-        override fun onChanged(newValue: String?) {
-            SecurityUtils.createKey(appPreferences.screenLockTimeout)
-        }
-    }
-
-    private inner class ScreenLockListener : OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(newValue: Boolean) {
-            binding.settingsScreenLockTimeout.isEnabled = newValue
-            if (newValue) {
-                binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
-            } else {
-                binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
+    private fun observeScreenLock() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.isScreenLocked
+            screenLockFlow.collect { newBoolean ->
+                if (newBoolean != state) {
+                    state = newBoolean
+                    binding.settingsScreenLockTimeout.isEnabled = newBoolean
+                    if (newBoolean) {
+                        binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
+                    } else {
+                        binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
+                    }
+                }
             }
         }
     }
 
-    private inner class ScreenSecurityChangeListener : OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(newValue: Boolean) {
-            if (newValue) {
-                window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
-            } else {
-                window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+    private fun observeScreenSecurity() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.isScreenSecured
+            screenSecurityFlow.collect { newBoolean ->
+                if (newBoolean != state) {
+                    state = newBoolean
+                    if (newBoolean) {
+                        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+                    } else {
+                        window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+                    }
+                }
             }
         }
     }
 
-    private inner class ProxyCredentialsChangeListener : OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(newValue: Boolean) {
-            if (newValue) {
-                showProxyCredentials()
-            } else {
-                hideProxyCredentials()
+    private fun observeProxyCredential() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.proxyCredentials
+            proxyCredentialFlow.collect { newBoolean ->
+                if (newBoolean != state) {
+                    state = newBoolean
+                    if (newBoolean) {
+                        showProxyCredentials()
+                    } else {
+                        hideProxyCredentials()
+                    }
+                }
             }
         }
     }
 
-    private inner class ProxyTypeChangeListener : OnPreferenceValueChangedListener<String> {
-        @Suppress("Detekt.TooGenericExceptionCaught")
-        override fun onChanged(newValue: String) {
-            if (("No proxy" == newValue)) {
-                hideProxySettings()
-            } else {
-                when (newValue) {
-                    "HTTP" -> {
-                        binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_http_value))
-                        appPreferences.proxyPort = getString(R.string.nc_settings_http_value)
-                    }
-
-                    "DIRECT" -> {
-                        binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_direct_value))
-                        appPreferences.proxyPort = getString(R.string.nc_settings_direct_value)
-                    }
-                    "SOCKS" -> {
-                        binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_socks_value))
-                        appPreferences.proxyPort = getString(R.string.nc_settings_socks_value)
-                    }
-                    else -> {
+    private fun observeProxyType() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.proxyType
+            proxyTypeFlow.collect { newString ->
+                if (newString != state) {
+                    state = newString
+                    if (("No proxy" == newString) || newString.isEmpty()) {
+                        hideProxySettings()
+                    } else {
+                        when (newString) {
+                            "HTTP" -> {
+                                binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_http_value))
+                                appPreferences.proxyPort = getString(R.string.nc_settings_http_value)
+                            }
+
+                            "DIRECT" -> {
+                                binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_direct_value))
+                                appPreferences.proxyPort = getString(R.string.nc_settings_direct_value)
+                            }
+
+                            "SOCKS" -> {
+                                binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_socks_value))
+                                appPreferences.proxyPort = getString(R.string.nc_settings_socks_value)
+                            }
+
+                            else -> {
+                            }
+                        }
+                        showProxySettings()
                     }
                 }
-                showProxySettings()
             }
         }
     }
 
-    private inner class ThemeChangeListener : OnPreferenceValueChangedListener<String?> {
-        override fun onChanged(newValue: String?) {
-            setAppTheme((newValue)!!)
-        }
-    }
-
-    private inner class PhoneBookIntegrationChangeListener(private val activity: Activity) :
-        OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(isEnabled: Boolean) {
-            if (isEnabled) {
-                if (checkPermission(activity, (context))) {
-                    checkForPhoneNumber()
+    private fun observeTheme() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.theme
+            themeFlow.collect { newString ->
+                if (newString != state) {
+                    state = newString
+                    setAppTheme(newString)
                 }
-            } else {
-                deleteAll()
             }
         }
     }
@@ -1112,68 +1127,80 @@ class SettingsActivity : BaseActivity() {
             })
     }
 
-    private inner class ReadPrivacyChangeListener : OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(newValue: Boolean) {
-            val booleanValue = if (newValue) "0" else "1"
-            val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
-            ncApi.setReadStatusPrivacy(
-                ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
-                json.toRequestBody("application/json".toMediaTypeOrNull())
-            )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(object : Observer<GenericOverall> {
-                    override fun onSubscribe(d: Disposable) {
-                        // unused atm
-                    }
-
-                    override fun onNext(genericOverall: GenericOverall) {
-                        // unused atm
-                    }
-
-                    override fun onError(e: Throwable) {
-                        appPreferences.setReadPrivacy(!newValue)
-                        binding.settingsReadPrivacySwitch.isChecked = !newValue
-                    }
-
-                    override fun onComplete() {
-                        // unused atm
-                    }
-                })
+    private fun observeReadPrivacy() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.readPrivacy
+            readPrivacyFlow.collect { newBoolean ->
+                if (state != newBoolean) {
+                    state = newBoolean
+                    val booleanValue = if (newBoolean) "0" else "1"
+                    val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
+                    ncApi.setReadStatusPrivacy(
+                        ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
+                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
+                        json.toRequestBody("application/json".toMediaTypeOrNull())
+                    )
+                        .subscribeOn(Schedulers.io())
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(object : Observer<GenericOverall> {
+                            override fun onSubscribe(d: Disposable) {
+                                // unused atm
+                            }
+
+                            override fun onNext(genericOverall: GenericOverall) {
+                                // unused atm
+                            }
+
+                            override fun onError(e: Throwable) {
+                                appPreferences.setReadPrivacy(!newBoolean)
+                                binding.settingsReadPrivacySwitch.isChecked = !newBoolean
+                            }
+
+                            override fun onComplete() {
+                                // unused atm
+                            }
+                        })
+                }
+            }
         }
     }
 
-    private inner class TypingStatusChangeListener : OnPreferenceValueChangedListener<Boolean> {
-        override fun onChanged(newValue: Boolean) {
-            val booleanValue = if (newValue) "0" else "1"
-            val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
-            ncApi.setTypingStatusPrivacy(
-                ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
-                json.toRequestBody("application/json".toMediaTypeOrNull())
-            )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(object : Observer<GenericOverall> {
-                    override fun onSubscribe(d: Disposable) {
-                        // unused atm
-                    }
-
-                    override fun onNext(genericOverall: GenericOverall) {
-                        loadCapabilitiesAndUpdateSettings()
-                        Log.i(TAG, "onNext called typing status set")
-                    }
-
-                    override fun onError(e: Throwable) {
-                        appPreferences.setTypingStatus(!newValue)
-                        binding.settingsTypingStatusSwitch.isChecked = !newValue
-                    }
-
-                    override fun onComplete() {
-                        // unused atm
-                    }
-                })
+    private fun observeTypingStatus() {
+        CoroutineScope(Dispatchers.Main).launch {
+            var state = appPreferences.typingStatus
+            typingStatusFlow.collect { newBoolean ->
+                if (state != newBoolean) {
+                    state = newBoolean
+                    val booleanValue = if (newBoolean) "0" else "1"
+                    val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
+                    ncApi.setTypingStatusPrivacy(
+                        ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
+                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
+                        json.toRequestBody("application/json".toMediaTypeOrNull())
+                    )
+                        .subscribeOn(Schedulers.io())
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(object : Observer<GenericOverall> {
+                            override fun onSubscribe(d: Disposable) {
+                                // unused atm
+                            }
+
+                            override fun onNext(genericOverall: GenericOverall) {
+                                loadCapabilitiesAndUpdateSettings()
+                                Log.i(TAG, "onNext called typing status set")
+                            }
+
+                            override fun onError(e: Throwable) {
+                                appPreferences.typingStatus = !newBoolean
+                                binding.settingsTypingStatusSwitch.isChecked = !newBoolean
+                            }
+
+                            override fun onComplete() {
+                                // unused atm
+                            }
+                        })
+                }
+            }
         }
     }
 

+ 5 - 191
app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java

@@ -26,333 +26,147 @@ package com.nextcloud.talk.utils.preferences;
 
 import android.annotation.SuppressLint;
 
-import com.nextcloud.talk.R;
-
-import net.orange_box.storebox.annotations.method.ClearMethod;
-import net.orange_box.storebox.annotations.method.DefaultValue;
-import net.orange_box.storebox.annotations.method.KeyByResource;
-import net.orange_box.storebox.annotations.method.KeyByString;
-import net.orange_box.storebox.annotations.method.RegisterChangeListenerMethod;
-import net.orange_box.storebox.annotations.method.RemoveMethod;
-import net.orange_box.storebox.annotations.method.UnregisterChangeListenerMethod;
-import net.orange_box.storebox.annotations.option.SaveOption;
-import net.orange_box.storebox.enums.SaveMode;
-import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
-
 @SuppressLint("NonConstantResourceId")
-@SaveOption(SaveMode.APPLY)
 public interface AppPreferences {
 
-    @KeyByString("proxy_type")
-    @RegisterChangeListenerMethod
-    void registerProxyTypeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByString("proxy_type")
-    @UnregisterChangeListenerMethod
-    void unregisterProxyTypeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByString("proxy_type")
     String getProxyType();
 
-    @KeyByString("proxy_type")
     void setProxyType(String proxyType);
 
-    @KeyByString("proxy_server")
-    @RemoveMethod
     void removeProxyType();
 
-    @KeyByString("proxy_host")
     String getProxyHost();
 
-    @KeyByString("proxy_host")
     void setProxyHost(String proxyHost);
 
-    @KeyByString("proxy_host")
-    @RemoveMethod
     void removeProxyHost();
 
-    @KeyByString("proxy_port")
     String getProxyPort();
 
-    @KeyByString("proxy_port")
     void setProxyPort(String proxyPort);
 
-    @KeyByString("proxy_port")
-    @RemoveMethod
     void removeProxyPort();
 
-    @KeyByString("proxy_credentials")
-    @RegisterChangeListenerMethod
-    void registerProxyCredentialsListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("proxy_credentials")
-    @UnregisterChangeListenerMethod
-    void unregisterProxyCredentialsListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("proxy_credentials")
     boolean getProxyCredentials();
 
-    @KeyByString("proxy_credentials")
     void setProxyNeedsCredentials(boolean proxyNeedsCredentials);
 
-    @KeyByString("proxy_credentials")
-    @RemoveMethod
     void removeProxyCredentials();
 
-    @KeyByString("proxy_username")
     String getProxyUsername();
 
-    @KeyByString("proxy_username")
     void setProxyUsername(String proxyUsername);
 
-    @KeyByString("proxy_username")
-    @RemoveMethod
     void removeProxyUsername();
 
-    @KeyByString("proxy_password")
     String getProxyPassword();
 
-    @KeyByString("proxy_password")
     void setProxyPassword(String proxyPassword);
 
-    @KeyByString("proxy_password")
-    @RemoveMethod
     void removeProxyPassword();
 
-    @KeyByString("push_token")
     String getPushToken();
 
-    @KeyByString("push_token")
     void setPushToken(String pushToken);
 
-    @KeyByString("push_token")
-    @RemoveMethod
     void removePushToken();
 
-    @KeyByString("tempClientCertAlias")
     String getTemporaryClientCertAlias();
 
-    @KeyByString("tempClientCertAlias")
     void setTemporaryClientCertAlias(String alias);
 
-    @KeyByString("tempClientCertAlias")
-    @RemoveMethod
     void removeTemporaryClientCertAlias();
 
-    @KeyByString("pushToTalk_intro_shown")
     boolean getPushToTalkIntroShown();
 
-    @KeyByString("pushToTalk_intro_shown")
     void setPushToTalkIntroShown(boolean shown);
 
-    @KeyByString("pushToTalk_intro_shown")
-    @RemoveMethod
     void removePushToTalkIntroShown();
 
-    @KeyByString("call_ringtone")
     String getCallRingtoneUri();
 
-    @KeyByString("call_ringtone")
     void setCallRingtoneUri(String value);
 
-    @KeyByString("call_ringtone")
-    @RemoveMethod
     void removeCallRingtoneUri();
 
-    @KeyByString("message_ringtone")
     String getMessageRingtoneUri();
 
-    @KeyByString("message_ringtone")
     void setMessageRingtoneUri(String value);
 
-    @KeyByString("message_ringtone")
-    @RemoveMethod
     void removeMessageRingtoneUri();
 
-    @KeyByString("notification_channels_upgrade_to_v2")
     boolean getIsNotificationChannelUpgradedToV2();
 
-    @KeyByString("notification_channels_upgrade_to_v2")
     void setNotificationChannelIsUpgradedToV2(boolean value);
 
-    @KeyByString("notification_channels_upgrade_to_v2")
-    @RemoveMethod
     void removeNotificationChannelUpgradeToV2();
 
-    @KeyByString("notification_channels_upgrade_to_v3")
     boolean getIsNotificationChannelUpgradedToV3();
 
-    @KeyByString("notification_channels_upgrade_to_v3")
     void setNotificationChannelIsUpgradedToV3(boolean value);
 
-    @KeyByString("notification_channels_upgrade_to_v3")
-    @RemoveMethod
     void removeNotificationChannelUpgradeToV3();
 
-    @KeyByString("screen_security")
-    @DefaultValue(R.bool.value_false)
     boolean getIsScreenSecured();
 
-    @KeyByString("screen_security")
     void setScreenSecurity(boolean value);
 
-    @KeyByString("screen_security")
-    @RemoveMethod
     void removeScreenSecurity();
 
-    @KeyByString("screen_security")
-    @RegisterChangeListenerMethod
-    void registerScreenSecurityListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("screen_security")
-    @UnregisterChangeListenerMethod
-    void unregisterScreenSecurityListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("screen_lock")
-    @DefaultValue(R.bool.value_false)
     boolean getIsScreenLocked();
 
-    @KeyByString("screen_lock")
     void setScreenLock(boolean value);
 
-    @KeyByString("screen_lock")
-    @RemoveMethod
     void removeScreenLock();
 
-    @KeyByString("screen_lock")
-    @RegisterChangeListenerMethod
-    void registerScreenLockListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("screen_lock")
-    @UnregisterChangeListenerMethod
-    void unregisterScreenLockListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByString("incognito_keyboard")
-    @DefaultValue(R.bool.value_true)
     boolean getIsKeyboardIncognito();
 
-    @KeyByString("incognito_keyboard")
     void setIncognitoKeyboard(boolean value);
 
-    @KeyByString("incognito_keyboard")
-    @RemoveMethod
     void removeIncognitoKeyboard();
-    
-    @KeyByString("phone_book_integration")
-    @DefaultValue(R.bool.value_false)
+
     boolean isPhoneBookIntegrationEnabled();
-    
-    @KeyByString("phone_book_integration") 
+
     void setPhoneBookIntegration(boolean value);
 
     // TODO Remove in 13.0.0
-    @KeyByString("link_previews")
-    @RemoveMethod
     void removeLinkPreviews();
 
-    @KeyByString("screen_lock_timeout")
-    @DefaultValue(R.string.nc_screen_lock_timeout_sixty)
     String getScreenLockTimeout();
 
-    @KeyByString("screen_lock_timeout")
     void setScreenLockTimeout(String value);
 
-    @KeyByString("screen_lock_timeout")
-    @RemoveMethod
     void removeScreenLockTimeout();
 
-    @KeyByString("screen_lock_timeout")
-    @RegisterChangeListenerMethod
-    void registerScreenLockTimeoutListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByString("screen_lock_timeout")
-    @UnregisterChangeListenerMethod
-    void unregisterScreenLockTimeoutListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByResource(R.string.nc_settings_theme_key)
-    @DefaultValue(R.string.nc_default_theme)
     String getTheme();
 
-    @KeyByResource(R.string.nc_settings_theme_key)
     void setTheme(String newValue);
 
-    @KeyByResource(R.string.nc_settings_theme_key)
-    @RemoveMethod
     void removeTheme();
 
-    @KeyByResource(R.string.nc_settings_theme_key)
-    @RegisterChangeListenerMethod
-    void registerThemeChangeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByResource(R.string.nc_settings_theme_key)
-    @UnregisterChangeListenerMethod
-    void unregisterThemeChangeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByString("db_cypher_v4_upgrade")
-    @DefaultValue(R.bool.value_true)
     boolean isDbCypherToUpgrade();
 
-    @KeyByString("db_cypher_v4_upgrade")
     void setDbCypherToUpgrade(boolean value);
 
-    @KeyByString("db_room_migrated")
-    @DefaultValue(R.bool.value_false)
     boolean getIsDbRoomMigrated();
 
-    @KeyByString("db_room_migrated")
     void setIsDbRoomMigrated(boolean value);
-    
-    @KeyByResource(R.string.nc_settings_phone_book_integration_key)
-    @RegisterChangeListenerMethod
-    void registerPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByResource(R.string.nc_settings_phone_book_integration_key)
-    @UnregisterChangeListenerMethod
-    void unregisterPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
 
-    @KeyByString("phone_book_integration_last_run")
     void setPhoneBookIntegrationLastRun(long currentTimeMillis);
 
-    @KeyByString("phone_book_integration_last_run")
     long getPhoneBookIntegrationLastRun(Long defaultValue);
 
-    @KeyByResource(R.string.nc_settings_read_privacy_key)
     void setReadPrivacy(boolean value);
 
-    @KeyByString("typing_status")
-    void setTypingStatus(boolean value);
-    
-    @KeyByResource(R.string.nc_settings_read_privacy_key)
-    @RegisterChangeListenerMethod
-    void registerReadPrivacyChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
-
-    @KeyByResource(R.string.nc_settings_read_privacy_key)
-    @UnregisterChangeListenerMethod
-    void unregisterReadPrivacyChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
+    boolean getReadPrivacy();
 
-    @KeyByString("typing_status")
-    @RegisterChangeListenerMethod
-    void registerTypingStatusChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
+    void setTypingStatus(boolean value);
 
-    @KeyByString("typing_status")
-    @UnregisterChangeListenerMethod
-    void unregisterTypingStatusChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
+    boolean getTypingStatus();
 
-    @KeyByResource(R.string.nc_file_browser_sort_by_key)
     void setSorting(String value);
 
-    @KeyByResource(R.string.nc_file_browser_sort_by_key)
-    @DefaultValue(R.string.nc_file_browser_sort_by_default)
     String getSorting();
 
-    @KeyByResource(R.string.nc_file_browser_sort_by_key)
-    @RegisterChangeListenerMethod
-    void registerSortingChangeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @KeyByResource(R.string.nc_file_browser_sort_by_key)
-    @UnregisterChangeListenerMethod
-    void unregisterSortingChangeListener(OnPreferenceValueChangedListener<String> listener);
-
-    @ClearMethod
     void clear();
 }

+ 491 - 0
app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt

@@ -0,0 +1,491 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Julius Linus
+ * Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.utils.preferences
+
+import android.content.Context
+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.longPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import com.nextcloud.talk.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
+
+@ExperimentalCoroutinesApi
+@Suppress("TooManyFunctions", "DeferredResultUnused", "EmptyFunctionBlock")
+class AppPreferencesImpl(val context: Context) : AppPreferences {
+
+    override fun getProxyType(): String {
+        return runBlocking { async { readString(PROXY_TYPE, "No proxy").first() } }.getCompleted()
+    }
+
+    override fun setProxyType(proxyType: String?) = runBlocking<Unit> {
+        async {
+            if (proxyType != null) {
+                writeString(PROXY_TYPE, proxyType)
+            }
+        }
+    }
+
+    override fun removeProxyType() {
+        proxyType = ""
+    }
+
+    override fun getProxyHost(): String {
+        return runBlocking { async { readString(PROXY_HOST).first() } }.getCompleted()
+    }
+
+    override fun setProxyHost(proxyHost: String?) = runBlocking<Unit> {
+        async {
+            if (proxyHost != null) {
+                writeString(PROXY_HOST, proxyHost)
+            }
+        }
+    }
+
+    override fun removeProxyHost() {
+        proxyHost = ""
+    }
+
+    override fun getProxyPort(): String {
+        return runBlocking { async { readString(PROXY_PORT).first() } }.getCompleted()
+    }
+
+    override fun setProxyPort(proxyPort: String?) = runBlocking<Unit> {
+        async {
+            if (proxyPort != null) {
+                writeString(PROXY_PORT, proxyPort)
+            }
+        }
+    }
+
+    override fun removeProxyPort() {
+        proxyPort = ""
+    }
+
+    override fun getProxyCredentials(): Boolean {
+        return runBlocking { async { readBoolean(PROXY_CRED).first() } }.getCompleted()
+    }
+
+    override fun setProxyNeedsCredentials(proxyNeedsCredentials: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(PROXY_CRED, proxyNeedsCredentials)
+        }
+    }
+
+    override fun removeProxyCredentials() {
+        setProxyNeedsCredentials(false)
+    }
+
+    override fun getProxyUsername(): String {
+        return runBlocking { async { readString(PROXY_USERNAME).first() } }.getCompleted()
+    }
+
+    override fun setProxyUsername(proxyUsername: String?) = runBlocking<Unit> {
+        async {
+            if (proxyUsername != null) {
+                writeString(PROXY_USERNAME, proxyUsername)
+            }
+        }
+    }
+
+    override fun removeProxyUsername() {
+        proxyUsername = ""
+    }
+
+    override fun getProxyPassword(): String {
+        return runBlocking { async { readString(PROXY_PASSWORD).first() } }.getCompleted()
+    }
+
+    override fun setProxyPassword(proxyPassword: String?) = runBlocking<Unit> {
+        async {
+            if (proxyPassword != null) {
+                writeString(PROXY_PASSWORD, proxyPassword)
+            }
+        }
+    }
+
+    override fun removeProxyPassword() {
+        proxyPassword = ""
+    }
+
+    override fun getPushToken(): String {
+        return runBlocking { async { readString(PUSH_TOKEN).first() } }.getCompleted()
+    }
+
+    override fun setPushToken(pushToken: String?) = runBlocking<Unit> {
+        async {
+            if (pushToken != null) {
+                writeString(PUSH_TOKEN, pushToken)
+            }
+        }
+    }
+
+    override fun removePushToken() {
+        pushToken = ""
+    }
+
+    override fun getTemporaryClientCertAlias(): String {
+        return runBlocking { async { readString(TEMP_CLIENT_CERT_ALIAS).first() } }.getCompleted()
+    }
+
+    override fun setTemporaryClientCertAlias(alias: String?) = runBlocking<Unit> {
+        async {
+            if (alias != null) {
+                writeString(TEMP_CLIENT_CERT_ALIAS, alias)
+            }
+        }
+    }
+
+    override fun removeTemporaryClientCertAlias() {
+        temporaryClientCertAlias = ""
+    }
+
+    override fun getPushToTalkIntroShown(): Boolean {
+        return runBlocking { async { readBoolean(PUSH_TO_TALK_INTRO_SHOWN).first() } }.getCompleted()
+    }
+
+    override fun setPushToTalkIntroShown(shown: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(PUSH_TO_TALK_INTRO_SHOWN, shown)
+        }
+    }
+
+    override fun removePushToTalkIntroShown() {
+        pushToTalkIntroShown = false
+    }
+
+    override fun getCallRingtoneUri(): String {
+        return runBlocking { async { readString(CALL_RINGTONE).first() } }.getCompleted()
+    }
+
+    override fun setCallRingtoneUri(value: String?) = runBlocking<Unit> {
+        async {
+            if (value != null) {
+                writeString(CALL_RINGTONE, value)
+            }
+        }
+    }
+
+    override fun removeCallRingtoneUri() {
+        callRingtoneUri = ""
+    }
+
+    override fun getMessageRingtoneUri(): String {
+        return runBlocking { async { readString(MESSAGE_RINGTONE).first() } }.getCompleted()
+    }
+
+    override fun setMessageRingtoneUri(value: String?) = runBlocking<Unit> {
+        async {
+            if (value != null) {
+                writeString(MESSAGE_RINGTONE, value)
+            }
+        }
+    }
+
+    override fun removeMessageRingtoneUri() {
+        messageRingtoneUri = ""
+    }
+
+    override fun getIsNotificationChannelUpgradedToV2(): Boolean {
+        return runBlocking { async { readBoolean(NOTIFY_UPGRADE_V2).first() } }.getCompleted()
+    }
+
+    override fun setNotificationChannelIsUpgradedToV2(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(NOTIFY_UPGRADE_V2, value)
+        }
+    }
+
+    override fun removeNotificationChannelUpgradeToV2() {
+        setNotificationChannelIsUpgradedToV2(false)
+    }
+
+    override fun getIsNotificationChannelUpgradedToV3(): Boolean {
+        return runBlocking { async { readBoolean(NOTIFY_UPGRADE_V3).first() } }.getCompleted()
+    }
+
+    override fun setNotificationChannelIsUpgradedToV3(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(NOTIFY_UPGRADE_V3, value)
+        }
+    }
+
+    override fun removeNotificationChannelUpgradeToV3() {
+        setNotificationChannelIsUpgradedToV3(false)
+    }
+
+    override fun getIsScreenSecured(): Boolean {
+        return runBlocking { async { readBoolean(SCREEN_SECURITY).first() } }.getCompleted()
+    }
+
+    override fun setScreenSecurity(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(SCREEN_SECURITY, value)
+        }
+    }
+
+    override fun removeScreenSecurity() {
+        setScreenSecurity(false)
+    }
+
+    override fun getIsScreenLocked(): Boolean {
+        return runBlocking { async { readBoolean(SCREEN_LOCK).first() } }.getCompleted()
+    }
+
+    override fun setScreenLock(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(SCREEN_LOCK, value)
+        }
+    }
+
+    override fun removeScreenLock() {
+        setScreenLock(false)
+    }
+
+    override fun getIsKeyboardIncognito(): Boolean {
+        val read = runBlocking { async { readBoolean(INCOGNITO_KEYBOARD).first() } }.getCompleted()
+        return read
+    }
+
+    override fun setIncognitoKeyboard(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(INCOGNITO_KEYBOARD, value)
+        }
+    }
+
+    override fun removeIncognitoKeyboard() {
+        setIncognitoKeyboard(false)
+    }
+
+    override fun isPhoneBookIntegrationEnabled(): Boolean {
+        return runBlocking { async { readBoolean(PHONE_BOOK_INTEGRATION).first() } }.getCompleted()
+    }
+
+    override fun setPhoneBookIntegration(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(PHONE_BOOK_INTEGRATION, value)
+        }
+    }
+
+    override fun removeLinkPreviews() = runBlocking<Unit> {
+        async {
+            writeBoolean(LINK_PREVIEWS, false)
+        }
+    }
+
+    override fun getScreenLockTimeout(): String {
+        val default = context.resources.getString(R.string.nc_screen_lock_timeout_sixty)
+        val read = runBlocking { async { readString(SCREEN_LOCK_TIMEOUT).first() } }.getCompleted()
+        return read.ifEmpty { default }
+    }
+
+    override fun setScreenLockTimeout(value: String?) = runBlocking<Unit> {
+        async {
+            if (value != null) {
+                writeString(SCREEN_LOCK_TIMEOUT, value)
+            }
+        }
+    }
+
+    override fun removeScreenLockTimeout() {
+        screenLockTimeout = ""
+    }
+
+    override fun getTheme(): String {
+        val key = context.resources.getString(R.string.nc_settings_theme_key)
+        val default = context.resources.getString(R.string.nc_default_theme)
+        val read = runBlocking { async { readString(key).first() } }.getCompleted()
+        return read.ifEmpty { default }
+    }
+
+    override fun setTheme(value: String?) = runBlocking<Unit> {
+        async {
+            if (value != null) {
+                val key = context.resources.getString(R.string.nc_settings_theme_key)
+                writeString(key, value)
+            }
+        }
+    }
+
+    override fun removeTheme() {
+        theme = ""
+    }
+
+    override fun isDbCypherToUpgrade(): Boolean {
+        val read = runBlocking { async { readBoolean(DB_CYPHER_V4_UPGRADE).first() } }.getCompleted()
+        return read
+    }
+
+    override fun setDbCypherToUpgrade(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(DB_CYPHER_V4_UPGRADE, value)
+        }
+    }
+
+    override fun getIsDbRoomMigrated(): Boolean {
+        return runBlocking { async { readBoolean(DB_ROOM_MIGRATED).first() } }.getCompleted()
+    }
+
+    override fun setIsDbRoomMigrated(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(DB_ROOM_MIGRATED, value)
+        }
+    }
+
+    override fun setPhoneBookIntegrationLastRun(currentTimeMillis: Long) = runBlocking<Unit> {
+        async {
+            writeLong(PHONE_BOOK_INTEGRATION_LAST_RUN, currentTimeMillis)
+        }
+    }
+
+    override fun getPhoneBookIntegrationLastRun(defaultValue: Long?): Long {
+        val result = if (defaultValue != null) {
+            runBlocking { async { readLong(PHONE_BOOK_INTEGRATION_LAST_RUN, defaultValue = defaultValue).first() } }
+                .getCompleted()
+        } else {
+            runBlocking { async { readLong(PHONE_BOOK_INTEGRATION_LAST_RUN).first() } }.getCompleted()
+        }
+        return result
+    }
+
+    override fun setReadPrivacy(value: Boolean) = runBlocking<Unit> {
+        val key = context.resources.getString(R.string.nc_settings_read_privacy_key)
+        async {
+            writeBoolean(key, value)
+        }
+    }
+
+    override fun getReadPrivacy(): Boolean {
+        val key = context.resources.getString(R.string.nc_settings_read_privacy_key)
+        return runBlocking { async { readBoolean(key).first() } }.getCompleted()
+    }
+
+    override fun setTypingStatus(value: Boolean) = runBlocking<Unit> {
+        async {
+            writeBoolean(TYPING_STATUS, value)
+        }
+    }
+
+    override fun getTypingStatus(): Boolean {
+        return runBlocking { async { readBoolean(TYPING_STATUS).first() } }.getCompleted()
+    }
+
+    override fun setSorting(value: String?) = runBlocking<Unit> {
+        val key = context.resources.getString(R.string.nc_file_browser_sort_by_key)
+        async {
+            if (value != null) {
+                writeString(key, value)
+            }
+        }
+    }
+
+    override fun getSorting(): String {
+        val key = context.resources.getString(R.string.nc_file_browser_sort_by_key)
+        val default = context.resources.getString(R.string.nc_file_browser_sort_by_default)
+        val read = runBlocking { async { readString(key).first() } }.getCompleted()
+        return read.ifEmpty { default }
+    }
+
+    override fun clear() {}
+
+    private suspend fun writeString(key: String, value: String) = context.dataStore.edit { settings ->
+        settings[
+            stringPreferencesKey(
+                key
+            )
+        ] = value
+    }
+
+    /**
+     * Returns a Flow of type String
+     * @param key the key of the persisted data to be observed
+     */
+    fun readString(key: String, defaultValue: String = ""): Flow<String> = context.dataStore.data.map { preferences ->
+        preferences[stringPreferencesKey(key)] ?: defaultValue
+    }
+
+    private suspend fun writeBoolean(key: String, value: Boolean) = context.dataStore.edit { settings ->
+        settings[
+            booleanPreferencesKey(
+                key
+            )
+        ] = value
+    }
+
+    /**
+     * Returns a Flow of type Boolean
+     * @param key the key of the persisted data to be observed
+     */
+    fun readBoolean(key: String, defaultValue: Boolean = false): Flow<Boolean> =
+        context.dataStore.data.map { preferences ->
+            preferences[booleanPreferencesKey(key)] ?: defaultValue
+        }
+
+    private suspend fun writeLong(key: String, value: Long) = context.dataStore.edit { settings ->
+        settings[
+            longPreferencesKey(
+                key
+            )
+        ] = value
+    }
+
+    private fun readLong(key: String, defaultValue: Long = 0): Flow<Long> = context.dataStore.data.map { preferences ->
+        preferences[longPreferencesKey(key)] ?: defaultValue
+    }
+
+    companion object {
+        @Suppress("UnusedPrivateProperty")
+        private val TAG = AppPreferencesImpl::class.simpleName
+        private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
+        const val PROXY_TYPE = "proxy_type"
+        const val PROXY_SERVER = "proxy_server"
+        const val PROXY_HOST = "proxy_host"
+        const val PROXY_PORT = "proxy_port"
+        const val PROXY_CRED = "proxy_credentials"
+        const val PROXY_USERNAME = "proxy_username"
+        const val PROXY_PASSWORD = "proxy_password"
+        const val PUSH_TOKEN = "push_token"
+        const val TEMP_CLIENT_CERT_ALIAS = "tempClientCertAlias"
+        const val PUSH_TO_TALK_INTRO_SHOWN = "pushToTalk_intro_shown"
+        const val CALL_RINGTONE = "call_ringtone"
+        const val MESSAGE_RINGTONE = "message_ringtone"
+        const val NOTIFY_UPGRADE_V2 = "notification_channels_upgrade_to_v2"
+        const val NOTIFY_UPGRADE_V3 = "notification_channels_upgrade_to_v3"
+        const val SCREEN_SECURITY = "screen_security"
+        const val SCREEN_LOCK = "screen_lock"
+        const val INCOGNITO_KEYBOARD = "incognito_keyboard"
+        const val PHONE_BOOK_INTEGRATION = "phone_book_integration"
+        const val LINK_PREVIEWS = "link_previews"
+        const val SCREEN_LOCK_TIMEOUT = "screen_lock_timeout"
+        const val DB_CYPHER_V4_UPGRADE = "db_cypher_v4_upgrade"
+        const val DB_ROOM_MIGRATED = "db_room_migrated"
+        const val PHONE_BOOK_INTEGRATION_LAST_RUN = "phone_book_integration_last_run"
+        const val TYPING_STATUS = "typing_status"
+    }
+}

+ 0 - 2
app/src/main/res/values/strings.xml

@@ -147,7 +147,6 @@ How to translate with transifex:
     <string name="nc_settings_screen_security_key" translatable="false">screen_security</string>
     <string name="nc_settings_incognito_keyboard_title">Incognito keyboard</string>
     <string name="nc_settings_incognito_keyboard_desc">Instructs keyboard to disable personalized learning (without guarantees)</string>
-    <string name="nc_settings_incognito_keyboard_key" translatable="false">incognito_keyboard</string>
     <string name="nc_settings_read_privacy_key" translatable="false">read_privacy</string>
     <string name="nc_locked_tap_to_unlock">Tap to unlock</string>
     <string name="nc_locked">Locked</string>
@@ -510,7 +509,6 @@ How to translate with transifex:
     <string name="nc_voice_message_missing_audio_permission">Permission for audio recording is required</string>
 
     <!-- Phonebook Integration -->
-    <string name="nc_settings_phone_book_integration_key" translatable="false">phone_book_integration</string>
     <string name="nc_settings_phone_book_integration_desc">Match contacts based on phone number to integrate Talk shortcut into system contacts app</string>
     <string name="nc_settings_phone_book_integration_title">Phone number integration</string>
     <string name="nc_settings_phone_book_integration_phone_number_dialog_title">Phone number</string>