Migrating away from StoreBox to DataStore

- Added DataStore dependencies
- Created AppPreferencesImpl

Signed-off-by: Julius Linus <julius.linus@nextcloud.com>
This commit is contained in:
Julius Linus 2023-08-14 13:37:52 -05:00 committed by Marcel Hibbe
parent 128e57dbf7
commit 11f1d5fc7e
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
7 changed files with 742 additions and 394 deletions

View file

@ -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'

View file

@ -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,

View file

@ -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,18 +96,17 @@ 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() {

View file

@ -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,50 +899,65 @@ 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) {
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) {
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) {
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)) {
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 (newValue) {
when (newString) {
"HTTP" -> {
binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_http_value))
appPreferences.proxyPort = getString(R.string.nc_settings_http_value)
@ -949,10 +967,12 @@ class SettingsActivity : BaseActivity() {
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 -> {
}
}
@ -960,22 +980,17 @@ class SettingsActivity : BaseActivity() {
}
}
}
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,9 +1127,13 @@ class SettingsActivity : BaseActivity() {
})
}
private inner class ReadPrivacyChangeListener : OnPreferenceValueChangedListener<Boolean> {
override fun onChanged(newValue: Boolean) {
val booleanValue = if (newValue) "0" else "1"
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),
@ -1133,8 +1152,8 @@ class SettingsActivity : BaseActivity() {
}
override fun onError(e: Throwable) {
appPreferences.setReadPrivacy(!newValue)
binding.settingsReadPrivacySwitch.isChecked = !newValue
appPreferences.setReadPrivacy(!newBoolean)
binding.settingsReadPrivacySwitch.isChecked = !newBoolean
}
override fun onComplete() {
@ -1143,10 +1162,16 @@ class SettingsActivity : BaseActivity() {
})
}
}
}
}
private inner class TypingStatusChangeListener : OnPreferenceValueChangedListener<Boolean> {
override fun onChanged(newValue: Boolean) {
val booleanValue = if (newValue) "0" else "1"
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),
@ -1166,8 +1191,8 @@ class SettingsActivity : BaseActivity() {
}
override fun onError(e: Throwable) {
appPreferences.setTypingStatus(!newValue)
binding.settingsTypingStatusSwitch.isChecked = !newValue
appPreferences.typingStatus = !newBoolean
binding.settingsTypingStatusSwitch.isChecked = !newBoolean
}
override fun onComplete() {
@ -1176,6 +1201,8 @@ class SettingsActivity : BaseActivity() {
})
}
}
}
}
companion object {
private const val TAG = "SettingsController"

View file

@ -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")
boolean getReadPrivacy();
void setTypingStatus(boolean value);
@KeyByResource(R.string.nc_settings_read_privacy_key)
@RegisterChangeListenerMethod
void registerReadPrivacyChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
boolean getTypingStatus();
@KeyByResource(R.string.nc_settings_read_privacy_key)
@UnregisterChangeListenerMethod
void unregisterReadPrivacyChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
@KeyByString("typing_status")
@RegisterChangeListenerMethod
void registerTypingStatusChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
@KeyByString("typing_status")
@UnregisterChangeListenerMethod
void unregisterTypingStatusChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
@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();
}

View file

@ -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"
}
}

View file

@ -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>