talk-android/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt
Marcel Hibbe c2b99c591f
disable sound notification settings when main notification permission is is not permitted.
Otherwise user can't enable them anyway

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2024-11-26 15:24:48 +01:00

1393 lines
59 KiB
Kotlin

/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
* SPDX-FileCopyrightText: 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2021 Tim Krüger <t@timkrueger.me>
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.settings
import android.Manifest
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.PorterDuff
import android.graphics.drawable.ColorDrawable
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.security.KeyChain
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivitySettingsBinding
import com.nextcloud.talk.diagnose.DiagnoseActivity
import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.ContactAddressBookWorker
import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.checkPermission
import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.deleteAll
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
import com.nextcloud.talk.profile.ProfileActivity
import com.nextcloud.talk.ui.dialog.SetPhoneNumberDialogFragment
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SCROLL_TO_NOTIFICATION_CATEGORY
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.power.PowerManagerUtils
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.HttpException
import java.net.URI
import java.net.URISyntaxException
import java.util.Locale
import javax.inject.Inject
@Suppress("LargeClass", "TooManyFunctions")
@AutoInjector(NextcloudTalkApplication::class)
class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNumberDialogClickListener {
private lateinit var binding: ActivitySettingsBinding
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var ncApiCoroutines: NcApiCoroutines
@Inject
lateinit var userManager: UserManager
@Inject
lateinit var platformPermissionUtil: PlatformPermissionUtil
private var currentUser: User? = null
private var credentials: String? = 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
private var scrollToNotificationCategory: Boolean? = false
@SuppressLint("StringFormatInvalid")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
getCurrentUser()
handleIntent(intent)
setupLicenceSetting()
binding.settingsScreenLockSummary.text = String.format(
Locale.getDefault(),
resources!!.getString(R.string.nc_settings_screen_lock_desc),
resources!!.getString(R.string.nc_app_product_name)
)
setupDiagnose()
setupPrivacyUrl()
setupSourceCodeUrl()
binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
setupPhoneBookIntegration()
setupClientCertView()
}
private fun handleIntent(intent: Intent) {
val extras: Bundle? = intent.extras
scrollToNotificationCategory = extras?.getBoolean(KEY_SCROLL_TO_NOTIFICATION_CATEGORY)
}
override fun onResume() {
super.onResume()
supportActionBar?.show()
dispose(null)
loadCapabilitiesAndUpdateSettings()
binding.settingsVersion.setOnClickListener {
sendLogs()
}
if (!TextUtils.isEmpty(currentUser!!.clientCertificate)) {
binding.settingsClientCertTitle.setText(R.string.nc_client_cert_change)
} else {
binding.settingsClientCertTitle.setText(R.string.nc_client_cert_setup)
}
setupCheckables()
setupScreenLockSetting()
setupNotificationSettings()
setupProxyTypeSettings()
setupProxyCredentialSettings()
registerChangeListeners()
if (currentUser != null) {
binding.domainText.text = Uri.parse(currentUser!!.baseUrl).host
setupServerAgeWarning()
if (currentUser!!.displayName != null) {
binding.nameText.text = currentUser!!.displayName
}
DisplayUtils.loadAvatarImage(currentUser, binding.avatarImage, false)
setupProfileQueryDisposable()
binding.settingsRemoveAccount.setOnClickListener {
showRemoveAccountWarning()
}
}
setupMessageView()
binding.settingsName.visibility = View.VISIBLE
binding.settingsName.setOnClickListener {
val intent = Intent(this, ProfileActivity::class.java)
startActivity(intent)
}
themeTitles()
themeSwitchPreferences()
if (scrollToNotificationCategory == true) {
scrollToNotificationCategory()
}
}
private fun scrollToNotificationCategory() {
binding.scrollView.post {
val scrollViewLocation = IntArray(2)
val targetLocation = IntArray(2)
binding.scrollView.getLocationOnScreen(scrollViewLocation)
binding.settingsNotificationsCategory.getLocationOnScreen(targetLocation)
val offset = targetLocation[1] - scrollViewLocation[1]
binding.scrollView.smoothScrollBy(0, offset)
}
}
private fun loadCapabilitiesAndUpdateSettings() {
val capabilitiesWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java).build()
WorkManager.getInstance(context).enqueue(capabilitiesWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(capabilitiesWork.id)
.observe(this) { workInfo ->
if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
getCurrentUser()
setupCheckables()
}
}
}
private fun setupActionBar() {
setSupportActionBar(binding.settingsToolbar)
binding.settingsToolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
supportActionBar?.title = context.getString(R.string.nc_settings)
viewThemeUtils.material.themeToolbar(binding.settingsToolbar)
}
private fun getCurrentUser() {
currentUser = currentUserProvider.currentUser.blockingGet()
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
}
private fun setupPhoneBookIntegration() {
if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!,
SpreedFeatures.PHONEBOOK_SEARCH
)
) {
binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
} else {
binding.settingsPhoneBookIntegration.visibility = View.GONE
}
}
private fun setupNotificationSettings() {
setupNotificationSoundsSettings()
setupNotificationPermissionSettings()
}
@SuppressLint("StringFormatInvalid")
@Suppress("LongMethod")
private fun setupNotificationPermissionSettings() {
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE
setTroubleshootingClickListenersIfNecessary()
if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
binding.batteryOptimizationIgnored.text =
resources!!.getString(R.string.nc_diagnose_battery_optimization_ignored)
binding.batteryOptimizationIgnored.setTextColor(
resources.getColor(R.color.high_emphasis_text, null)
)
} else {
binding.batteryOptimizationIgnored.text =
resources!!.getString(R.string.nc_diagnose_battery_optimization_not_ignored)
binding.batteryOptimizationIgnored.setTextColor(resources.getColor(R.color.nc_darkRed, null))
binding.settingsBatteryOptimizationWrapper.setOnClickListener {
val dialogText = String.format(
context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
context.resources.getString(R.string.nc_app_name)
)
val dialogBuilder = MaterialAlertDialogBuilder(this)
.setTitle(R.string.nc_ignore_battery_optimization_dialog_title)
.setMessage(dialogText)
.setPositiveButton(R.string.nc_ok) { _, _ ->
startActivity(
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
)
}
.setNegativeButton(R.string.nc_common_dismiss, null)
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
}
// handle notification permission on API level >= 33
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (platformPermissionUtil.isPostNotificationsPermissionGranted()) {
binding.ncDiagnoseNotificationPermissionSubtitle.text =
resources.getString(R.string.nc_settings_notifications_granted)
binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
resources.getColor(R.color.high_emphasis_text, null)
)
binding.settingsCallSound.isEnabled = true
binding.settingsCallSound.alpha = ENABLED_ALPHA
binding.settingsMessageSound.isEnabled = true
binding.settingsMessageSound.alpha = ENABLED_ALPHA
} else {
binding.ncDiagnoseNotificationPermissionSubtitle.text =
resources.getString(R.string.nc_settings_notifications_declined)
binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
resources.getColor(R.color.nc_darkRed, null)
)
binding.settingsCallSound.isEnabled = false
binding.settingsCallSound.alpha = DISABLED_ALPHA
binding.settingsMessageSound.isEnabled = false
binding.settingsMessageSound.alpha = DISABLED_ALPHA
binding.settingsNotificationsPermissionWrapper.setOnClickListener {
requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION
)
}
}
} else {
binding.settingsNotificationsPermissionWrapper.visibility = View.GONE
}
} else {
binding.settingsGplayOnlyWrapper.visibility = View.GONE
binding.settingsGplayNotAvailable.visibility = View.VISIBLE
}
}
private fun setupNotificationSoundsSettings() {
if (NotificationUtils.isCallsNotificationChannelEnabled(this)) {
val callRingtoneUri = getCallRingtoneUri(context, (appPreferences))
binding.callsRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
binding.callsRingtone.text = getRingtoneName(context, callRingtoneUri)
} else {
binding.callsRingtone.setTextColor(
ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
)
binding.callsRingtone.text = resources!!.getString(R.string.nc_common_disabled)
}
if (NotificationUtils.isMessagesNotificationChannelEnabled(this)) {
val messageRingtoneUri = getMessageRingtoneUri(context, (appPreferences))
binding.messagesRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
binding.messagesRingtone.text = getRingtoneName(context, messageRingtoneUri)
} else {
binding.messagesRingtone.setTextColor(
ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
)
binding.messagesRingtone.text = resources!!.getString(R.string.nc_common_disabled)
}
binding.settingsCallSound.setOnClickListener {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID,
NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
)
startActivity(intent)
}
binding.settingsMessageSound.setOnClickListener {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID,
NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name
)
startActivity(intent)
}
}
private fun setTroubleshootingClickListenersIfNecessary() {
fun click() {
val dialogBuilder = MaterialAlertDialogBuilder(this)
.setTitle(R.string.nc_notifications_troubleshooting_dialog_title)
.setMessage(R.string.nc_notifications_troubleshooting_dialog_text)
.setNegativeButton(R.string.nc_diagnose_dialog_open_checklist) { _, _ ->
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(resources.getString(R.string.notification_checklist_url))
)
)
}
.setPositiveButton(R.string.nc_diagnose_dialog_open_dontkillmyapp_website) { _, _ ->
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(resources.getString(R.string.dontkillmyapp_url))
)
)
}
.setNeutralButton(R.string.nc_diagnose_dialog_open_diagnose) { _, _ ->
val intent = Intent(context, DiagnoseActivity::class.java)
startActivity(intent)
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (platformPermissionUtil.isPostNotificationsPermissionGranted() &&
PowerManagerUtils().isIgnoringBatteryOptimizations()
) {
binding.settingsNotificationsPermissionWrapper.setOnClickListener { click() }
binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
}
} else if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
}
}
private fun setupSourceCodeUrl() {
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_source_code_url))) {
binding.settingsSourceCode.setOnClickListener {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(resources!!.getString(R.string.nc_source_code_url))
)
)
}
} else {
binding.settingsSourceCode.visibility = View.GONE
}
}
private fun setupDiagnose() {
binding.diagnoseWrapper.setOnClickListener {
val intent = Intent(context, DiagnoseActivity::class.java)
startActivity(intent)
}
}
private fun setupPrivacyUrl() {
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_privacy_url))) {
binding.settingsPrivacy.setOnClickListener {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(resources!!.getString(R.string.nc_privacy_url))
)
)
}
} else {
binding.settingsPrivacy.visibility = View.GONE
}
}
private fun setupLicenceSetting() {
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) {
binding.settingsLicence.setOnClickListener {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(resources!!.getString(R.string.nc_gpl3_url))
)
)
}
} else {
binding.settingsLicence.visibility = View.GONE
}
}
private fun setupClientCertView() {
var host: String? = null
var port = -1
val uri: URI
try {
uri = URI(currentUser!!.baseUrl!!)
host = uri.host
port = uri.port
Log.d(TAG, "uri is $uri")
} catch (e: URISyntaxException) {
Log.e(TAG, "Failed to create uri")
}
binding.settingsClientCert.setOnClickListener {
KeyChain.choosePrivateKeyAlias(
this,
{ alias: String? ->
var finalAlias: String? = alias
runOnUiThread {
if (finalAlias != null) {
binding.settingsClientCertTitle.setText(R.string.nc_client_cert_change)
} else {
binding.settingsClientCertTitle.setText(R.string.nc_client_cert_setup)
}
}
if (finalAlias == null) {
finalAlias = ""
}
Log.d(TAG, "host: $host and port: $port")
currentUser!!.clientCertificate = finalAlias
userManager.updateOrCreateUser(currentUser!!)
},
arrayOf("RSA", "EC"),
null,
host,
port,
currentUser!!.clientCertificate
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun registerChangeListeners() {
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)
}
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
}
observeProxyType()
observeProxyCredential()
observeScreenSecurity()
observeScreenLock()
observeTheme()
observeReadPrivacy()
observeTypingStatus()
}
fun sendLogs() {
if (resources!!.getBoolean(R.bool.nc_is_debug)) {
sendMailWithAttachment((context))
}
}
private fun showRemoveAccountWarning() {
binding.messageText.context?.let {
val materialAlertDialogBuilder = MaterialAlertDialogBuilder(it)
.setTitle(R.string.nc_settings_remove_account)
.setMessage(R.string.nc_settings_remove_confirmation)
.setPositiveButton(R.string.nc_settings_remove) { _, _ ->
removeCurrentAccount()
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
// unused atm
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(
it,
materialAlertDialogBuilder
)
val dialog = materialAlertDialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
}
@SuppressLint("CheckResult", "StringFormatInvalid")
private fun removeCurrentAccount() {
userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
val text = String.format(
context.resources.getString(R.string.nc_deleted_user),
currentUser!!.displayName
)
Toast.makeText(
context,
text,
Toast.LENGTH_LONG
).show()
restartApp()
}
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
Toast.makeText(
context,
context.resources.getString(R.string.nc_common_error_sorry),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
restartApp()
}
else -> {}
}
}
}
private fun restartApp() {
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
}
private fun getRingtoneName(context: Context, ringtoneUri: Uri?): String {
return if (ringtoneUri == null) {
resources!!.getString(R.string.nc_settings_no_ringtone)
} else if ((NotificationUtils.DEFAULT_CALL_RINGTONE_URI == ringtoneUri.toString()) ||
(NotificationUtils.DEFAULT_MESSAGE_RINGTONE_URI == ringtoneUri.toString())
) {
resources!!.getString(R.string.nc_settings_default_ringtone)
} else {
val r = RingtoneManager.getRingtone(context, ringtoneUri)
r.getTitle(context)
}
}
private fun themeSwitchPreferences() {
binding.run {
listOf(
settingsShowNotificationWarningSwitch,
settingsScreenLockSwitch,
settingsScreenSecuritySwitch,
settingsIncognitoKeyboardSwitch,
settingsPhoneBookIntegrationSwitch,
settingsReadPrivacySwitch,
settingsTypingStatusSwitch,
settingsProxyUseCredentialsSwitch
).forEach(viewThemeUtils.talk::colorSwitch)
}
}
private fun themeTitles() {
binding.run {
listOf(
settingsNotificationsTitle,
settingsAboutTitle,
settingsAdvancedTitle,
settingsAppearanceTitle,
settingsPrivacyTitle
).forEach(viewThemeUtils.platform::colorTextView)
}
}
private fun setupProxyTypeSettings() {
if (appPreferences.proxyType == null) {
appPreferences.proxyType = resources.getString(R.string.nc_no_proxy)
}
binding.settingsProxyChoice.setText(appPreferences.proxyType)
binding.settingsProxyChoice.setSimpleItems(R.array.proxy_type_descriptions)
binding.settingsProxyChoice.setOnItemClickListener { _, _, position, _ ->
val entryVal = resources.getStringArray(R.array.proxy_type_descriptions)[position]
appPreferences.proxyType = entryVal
}
binding.settingsProxyHostEdit.setText(appPreferences.proxyHost)
binding.settingsProxyHostEdit.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
appPreferences.proxyHost = binding.settingsProxyHostEdit.text.toString()
}
}
binding.settingsProxyPortEdit.setText(appPreferences.proxyPort)
binding.settingsProxyPortEdit.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
appPreferences.proxyPort = binding.settingsProxyPortEdit.text.toString()
}
}
binding.settingsProxyUsernameEdit.setText(appPreferences.proxyUsername)
binding.settingsProxyUsernameEdit.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
appPreferences.proxyUsername = binding.settingsProxyUsernameEdit.text.toString()
}
}
binding.settingsProxyPasswordEdit.setText(appPreferences.proxyPassword)
binding.settingsProxyPasswordEdit.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
appPreferences.proxyPassword = binding.settingsProxyPasswordEdit.text.toString()
}
}
if (((context.resources.getString(R.string.nc_no_proxy)) == appPreferences.proxyType) ||
appPreferences.proxyType == null
) {
hideProxySettings()
} else {
showProxySettings()
}
}
private fun setupProxyCredentialSettings() {
if (appPreferences.proxyCredentials) {
showProxyCredentials()
} else {
hideProxyCredentials()
}
}
private fun setupMessageView() {
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
when (ApplicationWideMessageHolder.getInstance().messageType) {
ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED -> {
binding.messageText.let {
viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
it.text = resources!!.getString(R.string.nc_settings_account_updated)
binding.messageText.visibility = View.VISIBLE
}
}
ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> {
binding.messageText.let {
it.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
it.text = resources!!.getString(R.string.nc_settings_wrong_account)
binding.messageText.visibility = View.VISIBLE
viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
it.text = resources!!.getString(R.string.nc_Server_account_imported)
binding.messageText.visibility = View.VISIBLE
}
}
ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED -> {
binding.messageText.let {
viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
it.text = resources!!.getString(R.string.nc_Server_account_imported)
binding.messageText.visibility = View.VISIBLE
}
}
ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> {
binding.messageText.let {
it.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
it.text = resources!!.getString(R.string.nc_server_failed_to_import_account)
binding.messageText.visibility = View.VISIBLE
}
}
else -> binding.messageText.visibility = View.GONE
}
ApplicationWideMessageHolder.getInstance().messageType = null
binding.messageText.animate()
?.translationY(0f)
?.alpha(0.0f)
?.setDuration(DURATION)
?.setStartDelay(START_DELAY)
?.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
binding.messageText.visibility = View.GONE
}
})
} else {
binding.messageText.visibility = View.GONE
}
}
private fun setupProfileQueryDisposable() {
profileQueryDisposable = ncApi.getUserProfile(
credentials,
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ userProfileOverall: UserProfileOverall ->
var displayName: String? = null
if (!TextUtils.isEmpty(
userProfileOverall.ocs!!.data!!.displayName
)
) {
displayName = userProfileOverall.ocs!!.data!!.displayName
} else if (!TextUtils.isEmpty(
userProfileOverall.ocs!!.data!!.displayNameAlt
)
) {
displayName = userProfileOverall.ocs!!.data!!.displayNameAlt
}
if ((!TextUtils.isEmpty(displayName) && !(displayName == currentUser!!.displayName))) {
currentUser!!.displayName = displayName
userManager.updateOrCreateUser(currentUser!!)
binding.nameText.text = currentUser!!.displayName
}
},
{ dispose(profileQueryDisposable) },
{ dispose(profileQueryDisposable) }
)
}
private fun setupServerAgeWarning() {
when {
CapabilitiesUtil.isServerEOL(currentUser!!.serverVersion?.major) -> {
binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context), R.color.nc_darkRed))
binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
binding.serverAgeWarningIcon.setColorFilter(
ContextCompat.getColor((context), R.color.nc_darkRed),
PorterDuff.Mode.SRC_IN
)
}
CapabilitiesUtil.isServerAlmostEOL(currentUser!!.serverVersion?.major) -> {
binding.serverAgeWarningText.setTextColor(
ContextCompat.getColor((context), R.color.nc_darkYellow)
)
binding.serverAgeWarningText.setText(R.string.nc_settings_server_almost_eol)
binding.serverAgeWarningIcon.setColorFilter(
ContextCompat.getColor((context), R.color.nc_darkYellow),
PorterDuff.Mode.SRC_IN
)
}
else -> {
binding.serverAgeWarningTextCard.visibility = View.GONE
}
}
}
private fun setupCheckables() {
binding.settingsShowNotificationWarningSwitch.isChecked =
appPreferences.showNotificationWarning
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
binding.settingsShowNotificationWarning.setOnClickListener {
val isChecked = binding.settingsShowNotificationWarningSwitch.isChecked
binding.settingsShowNotificationWarningSwitch.isChecked = !isChecked
appPreferences.setShowNotificationWarning(!isChecked)
}
} else {
binding.settingsShowNotificationWarning.visibility = View.GONE
}
binding.settingsScreenSecuritySwitch.isChecked = appPreferences.isScreenSecured
binding.settingsIncognitoKeyboardSwitch.isChecked = appPreferences.isKeyboardIncognito
if (CapabilitiesUtil.isReadStatusAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtil.isReadStatusPrivate(currentUser!!)
} else {
binding.settingsReadPrivacy.visibility = View.GONE
}
setupTypingStatusSetting()
binding.settingsPhoneBookIntegrationSwitch.isChecked = appPreferences.isPhoneBookIntegrationEnabled
binding.settingsProxyUseCredentialsSwitch.isChecked = appPreferences.proxyCredentials
binding.settingsProxyUseCredentials.setOnClickListener {
val isChecked = binding.settingsProxyUseCredentialsSwitch.isChecked
binding.settingsProxyUseCredentialsSwitch.isChecked = !isChecked
appPreferences.setProxyNeedsCredentials(!isChecked)
}
binding.settingsScreenLockSwitch.isChecked = appPreferences.isScreenLocked
binding.settingsScreenLock.setOnClickListener {
val isChecked = binding.settingsScreenLockSwitch.isChecked
binding.settingsScreenLockSwitch.isChecked = !isChecked
appPreferences.setScreenLock(!isChecked)
}
binding.settingsReadPrivacy.setOnClickListener {
val isChecked = binding.settingsReadPrivacySwitch.isChecked
binding.settingsReadPrivacySwitch.isChecked = !isChecked
appPreferences.setReadPrivacy(!isChecked)
}
binding.settingsIncognitoKeyboard.setOnClickListener {
val isChecked = binding.settingsIncognitoKeyboardSwitch.isChecked
binding.settingsIncognitoKeyboardSwitch.isChecked = !isChecked
appPreferences.setIncognitoKeyboard(!isChecked)
}
binding.settingsPhoneBookIntegration.setOnClickListener {
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 {
val isChecked = binding.settingsScreenSecuritySwitch.isChecked
binding.settingsScreenSecuritySwitch.isChecked = !isChecked
appPreferences.setScreenSecurity(!isChecked)
}
binding.settingsTypingStatus.setOnClickListener {
val isChecked = binding.settingsTypingStatusSwitch.isChecked
binding.settingsTypingStatusSwitch.isChecked = !isChecked
appPreferences.setTypingStatus(!isChecked)
}
}
private fun setupTypingStatusSetting() {
if (currentUser!!.externalSignalingServer?.externalSignalingServer?.isNotEmpty() == true) {
binding.settingsTypingStatusOnlyWithHpb.visibility = View.GONE
Log.i(TAG, "Typing Status Available: ${CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)}")
if (CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)) {
binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtil.isTypingStatusPrivate(currentUser!!)
} else {
binding.settingsTypingStatus.visibility = View.GONE
}
} else {
Log.i(TAG, "Typing Status not Available")
binding.settingsTypingStatusSwitch.isChecked = false
binding.settingsTypingStatusOnlyWithHpb.visibility = View.VISIBLE
binding.settingsTypingStatus.isEnabled = false
binding.settingsTypingStatusOnlyWithHpb.alpha = DISABLED_ALPHA
binding.settingsTypingStatus.alpha = DISABLED_ALPHA
}
}
private fun setupScreenLockSetting() {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager.isKeyguardSecure) {
binding.settingsScreenLock.isEnabled = true
binding.settingsScreenLockTimeout.isEnabled = true
binding.settingsScreenLockSwitch.isChecked = appPreferences.isScreenLocked
binding.settingsScreenLockTimeoutLayoutDropdown.isEnabled = appPreferences.isScreenLocked
if (appPreferences.isScreenLocked) {
binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
} else {
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
}
binding.settingsScreenLock.alpha = ENABLED_ALPHA
} else {
binding.settingsScreenLock.isEnabled = false
binding.settingsScreenLockTimeoutLayoutDropdown.isEnabled = false
appPreferences.removeScreenLock()
appPreferences.removeScreenLockTimeout()
binding.settingsScreenLockSwitch.isChecked = false
binding.settingsScreenLock.alpha = DISABLED_ALPHA
binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
}
}
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)
super.onDestroy()
}
private fun hideProxySettings() {
appPreferences.removeProxyHost()
appPreferences.removeProxyPort()
appPreferences.removeProxyCredentials()
appPreferences.removeProxyUsername()
appPreferences.removeProxyPassword()
binding.settingsProxyHostLayout.visibility = View.GONE
binding.settingsProxyPortLayout.visibility = View.GONE
binding.settingsProxyUseCredentials.visibility = View.GONE
hideProxyCredentials()
}
private fun showProxySettings() {
binding.settingsProxyHostLayout.visibility =
View.VISIBLE
binding.settingsProxyPortLayout.visibility =
View.VISIBLE
binding.settingsProxyUseCredentials.visibility =
View.VISIBLE
if (binding.settingsProxyUseCredentialsSwitch.isChecked) showProxyCredentials()
}
private fun showProxyCredentials() {
binding.settingsProxyUsernameLayout.visibility =
View.VISIBLE
binding.settingsProxyPasswordLayout.visibility =
View.VISIBLE
}
private fun hideProxyCredentials() {
appPreferences.removeProxyUsername()
appPreferences.removeProxyPassword()
binding.settingsProxyUsernameLayout.visibility = View.GONE
binding.settingsProxyPasswordLayout.visibility = View.GONE
}
private fun dispose(disposable: Disposable?) {
if (disposable != null && !disposable.isDisposed) {
disposable.dispose()
} else if (disposable == null) {
disposeProfileQueryDisposable()
disposeDbQueryDisposable()
}
}
private fun disposeDbQueryDisposable() {
if (dbQueryDisposable != null && !dbQueryDisposable!!.isDisposed) {
dbQueryDisposable!!.dispose()
dbQueryDisposable = null
} else if (dbQueryDisposable != null) {
dbQueryDisposable = null
}
}
private fun disposeProfileQueryDisposable() {
if (profileQueryDisposable != null && !profileQueryDisposable!!.isDisposed) {
profileQueryDisposable!!.dispose()
profileQueryDisposable = null
} else if (profileQueryDisposable != null) {
profileQueryDisposable = null
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
ContactAddressBookWorker.REQUEST_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
WorkManager
.getInstance(this)
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java).build())
checkForPhoneNumber()
} else {
appPreferences.setPhoneBookIntegration(false)
binding.settingsPhoneBookIntegrationSwitch.isChecked = appPreferences.isPhoneBookIntegrationEnabled
Snackbar.make(
binding.root,
context.resources.getString(R.string.no_phone_book_integration_due_to_permissions),
Snackbar.LENGTH_LONG
).show()
}
}
ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_DENIED) {
try {
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to open notification settings as fallback", e)
}
}
}
}
}
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 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 fun observeProxyCredential() {
CoroutineScope(Dispatchers.Main).launch {
var state = appPreferences.proxyCredentials
proxyCredentialFlow.collect { newBoolean ->
if (newBoolean != state) {
state = newBoolean
if (newBoolean) {
showProxyCredentials()
} else {
hideProxyCredentials()
}
}
}
}
}
private fun observeProxyType() {
CoroutineScope(Dispatchers.Main).launch {
var state = appPreferences.proxyType
proxyTypeFlow.collect { newString ->
if (newString != state) {
state = newString
if (((context.resources.getString(R.string.nc_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()
}
}
}
}
}
private fun observeTheme() {
CoroutineScope(Dispatchers.Main).launch {
var state = appPreferences.theme
themeFlow.collect { newString ->
if (newString != state) {
state = newString
setAppTheme(newString)
}
}
}
}
private fun checkForPhoneNumber() {
ncApi.getUserData(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<UserProfileOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(userProfileOverall: UserProfileOverall) {
if (userProfileOverall.ocs!!.data!!.phone?.isEmpty() == true) {
askForPhoneNumber()
} else {
Log.d(TAG, "phone number already set")
}
}
override fun onError(e: Throwable) {
// unused atm
}
override fun onComplete() {
// unused atm
}
})
}
private fun askForPhoneNumber() {
val dialog = SetPhoneNumberDialogFragment.newInstance()
dialog.show(supportFragmentManager, SetPhoneNumberDialogFragment.TAG)
}
override fun onSubmitClick(textInputLayout: TextInputLayout, dialog: DialogInterface) {
setPhoneNumber(textInputLayout, dialog)
}
private fun setPhoneNumber(textInputLayout: TextInputLayout, dialog: DialogInterface) {
val phoneNumber = textInputLayout.editText!!.text.toString()
ncApi.setUserData(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
"phone",
phoneNumber
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(genericOverall: GenericOverall) {
when (val statusCode = genericOverall.ocs?.meta?.statusCode) {
HTTP_CODE_OK -> {
dialog.dismiss()
Snackbar.make(
binding.root,
getString(
R.string.nc_settings_phone_book_integration_phone_number_dialog_success
),
Snackbar.LENGTH_LONG
).show()
}
else -> {
textInputLayout.helperText = getString(
R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
)
Log.d(TAG, "failed to set phoneNumber. statusCode=$statusCode")
}
}
}
override fun onError(e: Throwable) {
textInputLayout.helperText = getString(
R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
)
Log.e(SetPhoneNumberDialogFragment.TAG, "setPhoneNumber error", e)
}
override fun onComplete() {
// unused atm
}
})
}
private fun observeReadPrivacy() {
lifecycleScope.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}"
withContext(Dispatchers.IO) {
try {
credentials?.let { credentials ->
ncApiCoroutines.setReadStatusPrivacy(
credentials,
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
json.toRequestBody("application/json".toMediaTypeOrNull())
)
Log.i(TAG, "reading status set")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
appPreferences.setReadPrivacy(!newBoolean)
binding.settingsReadPrivacySwitch.isChecked = !newBoolean
}
if (e is HttpException && e.code() == HTTP_ERROR_CODE_BAD_REQUEST) {
Log.e(TAG, "read_status_privacy : Key or value is invalid")
} else {
Log.e(TAG, "Error setting read status", e)
}
}
}
}
}
}
}
private fun observeTypingStatus() {
lifecycleScope.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}"
withContext(Dispatchers.IO) {
try {
credentials?.let { credentials ->
ncApiCoroutines.setTypingStatusPrivacy(
credentials,
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
json.toRequestBody("application/json".toMediaTypeOrNull())
)
}
withContext(Dispatchers.Main) {
loadCapabilitiesAndUpdateSettings()
Log.i(TAG, "typing status set")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
appPreferences.typingStatus = !newBoolean
binding.settingsTypingStatusSwitch.isChecked = !newBoolean
}
if (e is HttpException && e.code() == HTTP_ERROR_CODE_BAD_REQUEST) {
Log.e(TAG, "typing_privacy : Key or value is invalid")
} else {
Log.e(TAG, "Error setting typing status", e)
}
}
}
}
}
}
}
companion object {
private val TAG = SettingsActivity::class.java.simpleName
private const val DURATION: Long = 2500
private const val START_DELAY: Long = 5000
private const val DISABLED_ALPHA: Float = 0.38f
private const val ENABLED_ALPHA: Float = 1.0f
const val HTTP_CODE_OK: Int = 200
const val HTTP_ERROR_CODE_BAD_REQUEST: Int = 400
}
}