SettingsActivity.kt 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337
  1. /*
  2. * Nextcloud Talk - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
  5. * SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
  6. * SPDX-FileCopyrightText: 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
  7. * SPDX-FileCopyrightText: 2021 Tim Krüger <t@timkrueger.me>
  8. * SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
  9. * SPDX-License-Identifier: GPL-3.0-or-later
  10. */
  11. package com.nextcloud.talk.settings
  12. import android.Manifest
  13. import android.animation.Animator
  14. import android.animation.AnimatorListenerAdapter
  15. import android.annotation.SuppressLint
  16. import android.app.KeyguardManager
  17. import android.content.Context
  18. import android.content.DialogInterface
  19. import android.content.Intent
  20. import android.content.pm.PackageManager
  21. import android.graphics.PorterDuff
  22. import android.graphics.drawable.ColorDrawable
  23. import android.media.RingtoneManager
  24. import android.net.Uri
  25. import android.os.Build
  26. import android.os.Bundle
  27. import android.provider.Settings
  28. import android.security.KeyChain
  29. import android.text.TextUtils
  30. import android.util.Log
  31. import android.view.View
  32. import android.view.WindowManager
  33. import android.widget.Toast
  34. import androidx.appcompat.app.AlertDialog
  35. import androidx.core.content.ContextCompat
  36. import androidx.core.content.res.ResourcesCompat
  37. import androidx.core.view.ViewCompat
  38. import androidx.work.OneTimeWorkRequest
  39. import androidx.work.WorkInfo
  40. import androidx.work.WorkManager
  41. import autodagger.AutoInjector
  42. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  43. import com.google.android.material.snackbar.Snackbar
  44. import com.google.android.material.textfield.TextInputLayout
  45. import com.nextcloud.android.common.ui.theme.utils.ColorRole
  46. import com.nextcloud.talk.BuildConfig
  47. import com.nextcloud.talk.R
  48. import com.nextcloud.talk.activities.BaseActivity
  49. import com.nextcloud.talk.activities.MainActivity
  50. import com.nextcloud.talk.api.NcApi
  51. import com.nextcloud.talk.api.NcApiCoroutines
  52. import com.nextcloud.talk.application.NextcloudTalkApplication
  53. import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
  54. import com.nextcloud.talk.conversationlist.ConversationsListActivity
  55. import com.nextcloud.talk.data.user.model.User
  56. import com.nextcloud.talk.databinding.ActivitySettingsBinding
  57. import com.nextcloud.talk.diagnose.DiagnoseActivity
  58. import com.nextcloud.talk.jobs.AccountRemovalWorker
  59. import com.nextcloud.talk.jobs.CapabilitiesWorker
  60. import com.nextcloud.talk.jobs.ContactAddressBookWorker
  61. import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.checkPermission
  62. import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.deleteAll
  63. import com.nextcloud.talk.models.json.generic.GenericOverall
  64. import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
  65. import com.nextcloud.talk.profile.ProfileActivity
  66. import com.nextcloud.talk.ui.dialog.SetPhoneNumberDialogFragment
  67. import com.nextcloud.talk.users.UserManager
  68. import com.nextcloud.talk.utils.ApiUtils
  69. import com.nextcloud.talk.utils.CapabilitiesUtil
  70. import com.nextcloud.talk.utils.ClosedInterfaceImpl
  71. import com.nextcloud.talk.utils.DisplayUtils
  72. import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
  73. import com.nextcloud.talk.utils.NotificationUtils
  74. import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
  75. import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
  76. import com.nextcloud.talk.utils.SecurityUtils
  77. import com.nextcloud.talk.utils.SpreedFeatures
  78. import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
  79. import com.nextcloud.talk.utils.power.PowerManagerUtils
  80. import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
  81. import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
  82. import io.reactivex.Observer
  83. import io.reactivex.android.schedulers.AndroidSchedulers
  84. import io.reactivex.disposables.Disposable
  85. import io.reactivex.schedulers.Schedulers
  86. import kotlinx.coroutines.CoroutineScope
  87. import kotlinx.coroutines.Dispatchers
  88. import kotlinx.coroutines.ExperimentalCoroutinesApi
  89. import kotlinx.coroutines.flow.Flow
  90. import kotlinx.coroutines.launch
  91. import okhttp3.MediaType.Companion.toMediaTypeOrNull
  92. import okhttp3.RequestBody.Companion.toRequestBody
  93. import java.net.URI
  94. import java.net.URISyntaxException
  95. import java.util.Locale
  96. import javax.inject.Inject
  97. @Suppress("LargeClass", "TooManyFunctions")
  98. @AutoInjector(NextcloudTalkApplication::class)
  99. class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNumberDialogClickListener {
  100. private lateinit var binding: ActivitySettingsBinding
  101. @Inject
  102. lateinit var ncApi: NcApi
  103. @Inject
  104. lateinit var ncApiCoroutines: NcApiCoroutines
  105. @Inject
  106. lateinit var userManager: UserManager
  107. @Inject
  108. lateinit var platformPermissionUtil: PlatformPermissionUtil
  109. private var currentUser: User? = null
  110. private var credentials: String? = null
  111. private lateinit var proxyTypeFlow: Flow<String>
  112. private lateinit var proxyCredentialFlow: Flow<Boolean>
  113. private lateinit var screenSecurityFlow: Flow<Boolean>
  114. private lateinit var screenLockFlow: Flow<Boolean>
  115. private lateinit var screenLockTimeoutFlow: Flow<String>
  116. private lateinit var themeFlow: Flow<String>
  117. private lateinit var readPrivacyFlow: Flow<Boolean>
  118. private lateinit var typingStatusFlow: Flow<Boolean>
  119. private lateinit var phoneBookIntegrationFlow: Flow<Boolean>
  120. private var profileQueryDisposable: Disposable? = null
  121. private var dbQueryDisposable: Disposable? = null
  122. @SuppressLint("StringFormatInvalid")
  123. override fun onCreate(savedInstanceState: Bundle?) {
  124. super.onCreate(savedInstanceState)
  125. NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
  126. binding = ActivitySettingsBinding.inflate(layoutInflater)
  127. setupActionBar()
  128. setContentView(binding.root)
  129. setupSystemColors()
  130. binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
  131. getCurrentUser()
  132. setupLicenceSetting()
  133. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
  134. binding.settingsIncognitoKeyboard.visibility = View.GONE
  135. }
  136. binding.settingsScreenLockSummary.text = String.format(
  137. Locale.getDefault(),
  138. resources!!.getString(R.string.nc_settings_screen_lock_desc),
  139. resources!!.getString(R.string.nc_app_product_name)
  140. )
  141. setupDiagnose()
  142. setupPrivacyUrl()
  143. setupSourceCodeUrl()
  144. binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
  145. setupPhoneBookIntegration()
  146. setupClientCertView()
  147. }
  148. override fun onResume() {
  149. super.onResume()
  150. supportActionBar?.show()
  151. dispose(null)
  152. loadCapabilitiesAndUpdateSettings()
  153. binding.settingsVersion.setOnClickListener {
  154. sendLogs()
  155. }
  156. if (!TextUtils.isEmpty(currentUser!!.clientCertificate)) {
  157. binding.settingsClientCertTitle.setText(R.string.nc_client_cert_change)
  158. } else {
  159. binding.settingsClientCertTitle.setText(R.string.nc_client_cert_setup)
  160. }
  161. setupCheckables()
  162. setupScreenLockSetting()
  163. setupNotificationSettings()
  164. setupProxyTypeSettings()
  165. setupProxyCredentialSettings()
  166. registerChangeListeners()
  167. if (currentUser != null) {
  168. binding.domainText.text = Uri.parse(currentUser!!.baseUrl).host
  169. setupServerAgeWarning()
  170. if (currentUser!!.displayName != null) {
  171. binding.nameText.text = currentUser!!.displayName
  172. }
  173. DisplayUtils.loadAvatarImage(currentUser, binding.avatarImage, false)
  174. setupProfileQueryDisposable()
  175. binding.settingsRemoveAccount.setOnClickListener {
  176. showRemoveAccountWarning()
  177. }
  178. }
  179. setupMessageView()
  180. binding.settingsName.visibility = View.VISIBLE
  181. binding.settingsName.setOnClickListener {
  182. val intent = Intent(this, ProfileActivity::class.java)
  183. startActivity(intent)
  184. }
  185. themeTitles()
  186. themeSwitchPreferences()
  187. }
  188. private fun loadCapabilitiesAndUpdateSettings() {
  189. val capabilitiesWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java).build()
  190. WorkManager.getInstance(context).enqueue(capabilitiesWork)
  191. WorkManager.getInstance(context).getWorkInfoByIdLiveData(capabilitiesWork.id)
  192. .observe(this) { workInfo ->
  193. if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
  194. getCurrentUser()
  195. setupCheckables()
  196. }
  197. }
  198. }
  199. private fun setupActionBar() {
  200. setSupportActionBar(binding.settingsToolbar)
  201. binding.settingsToolbar.setNavigationOnClickListener {
  202. onBackPressedDispatcher.onBackPressed()
  203. }
  204. supportActionBar?.setDisplayHomeAsUpEnabled(true)
  205. supportActionBar?.setDisplayShowHomeEnabled(true)
  206. supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
  207. supportActionBar?.title = context.getString(R.string.nc_settings)
  208. viewThemeUtils.material.themeToolbar(binding.settingsToolbar)
  209. }
  210. private fun getCurrentUser() {
  211. currentUser = currentUserProvider.currentUser.blockingGet()
  212. credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
  213. }
  214. private fun setupPhoneBookIntegration() {
  215. if (CapabilitiesUtil.hasSpreedFeatureCapability(
  216. currentUser?.capabilities?.spreedCapability!!,
  217. SpreedFeatures.PHONEBOOK_SEARCH
  218. )
  219. ) {
  220. binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
  221. } else {
  222. binding.settingsPhoneBookIntegration.visibility = View.GONE
  223. }
  224. }
  225. private fun setupNotificationSettings() {
  226. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  227. binding.settingsNotificationsTitle.text = resources!!.getString(
  228. R.string.nc_settings_notification_sounds_post_oreo
  229. )
  230. }
  231. setupNotificationSoundsSettings()
  232. setupNotificationPermissionSettings()
  233. }
  234. @SuppressLint("StringFormatInvalid")
  235. @Suppress("LongMethod")
  236. private fun setupNotificationPermissionSettings() {
  237. if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
  238. binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE
  239. setTroubleshootingClickListenersIfNecessary()
  240. if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
  241. binding.batteryOptimizationIgnored.text =
  242. resources!!.getString(R.string.nc_diagnose_battery_optimization_ignored)
  243. binding.batteryOptimizationIgnored.setTextColor(
  244. resources.getColor(R.color.high_emphasis_text, null)
  245. )
  246. } else {
  247. binding.batteryOptimizationIgnored.text =
  248. resources!!.getString(R.string.nc_diagnose_battery_optimization_not_ignored)
  249. binding.batteryOptimizationIgnored.setTextColor(resources.getColor(R.color.nc_darkRed, null))
  250. binding.settingsBatteryOptimizationWrapper.setOnClickListener {
  251. val dialogText = String.format(
  252. context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
  253. context.resources.getString(R.string.nc_app_name)
  254. )
  255. val dialogBuilder = MaterialAlertDialogBuilder(this)
  256. .setTitle(R.string.nc_ignore_battery_optimization_dialog_title)
  257. .setMessage(dialogText)
  258. .setPositiveButton(R.string.nc_ok) { _, _ ->
  259. startActivity(
  260. Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
  261. )
  262. }
  263. .setNegativeButton(R.string.nc_common_dismiss, null)
  264. viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
  265. val dialog = dialogBuilder.show()
  266. viewThemeUtils.platform.colorTextButtons(
  267. dialog.getButton(AlertDialog.BUTTON_POSITIVE),
  268. dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
  269. )
  270. }
  271. }
  272. // handle notification permission on API level >= 33
  273. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
  274. if (platformPermissionUtil.isPostNotificationsPermissionGranted()) {
  275. binding.ncDiagnoseNotificationPermissionSubtitle.text =
  276. resources.getString(R.string.nc_settings_notifications_granted)
  277. binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
  278. resources.getColor(R.color.high_emphasis_text, null)
  279. )
  280. } else {
  281. binding.ncDiagnoseNotificationPermissionSubtitle.text =
  282. resources.getString(R.string.nc_settings_notifications_declined)
  283. binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
  284. resources.getColor(R.color.nc_darkRed, null)
  285. )
  286. binding.settingsNotificationsPermissionWrapper.setOnClickListener {
  287. requestPermissions(
  288. arrayOf(Manifest.permission.POST_NOTIFICATIONS),
  289. ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION
  290. )
  291. }
  292. }
  293. } else {
  294. binding.settingsNotificationsPermissionWrapper.visibility = View.GONE
  295. }
  296. } else {
  297. binding.settingsGplayOnlyWrapper.visibility = View.GONE
  298. binding.settingsGplayNotAvailable.visibility = View.VISIBLE
  299. }
  300. }
  301. private fun setupNotificationSoundsSettings() {
  302. if (NotificationUtils.isCallsNotificationChannelEnabled(this)) {
  303. val callRingtoneUri = getCallRingtoneUri(context, (appPreferences))
  304. binding.callsRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
  305. binding.callsRingtone.text = getRingtoneName(context, callRingtoneUri)
  306. } else {
  307. binding.callsRingtone.setTextColor(
  308. ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
  309. )
  310. binding.callsRingtone.text = resources!!.getString(R.string.nc_common_disabled)
  311. }
  312. if (NotificationUtils.isMessagesNotificationChannelEnabled(this)) {
  313. val messageRingtoneUri = getMessageRingtoneUri(context, (appPreferences))
  314. binding.messagesRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
  315. binding.messagesRingtone.text = getRingtoneName(context, messageRingtoneUri)
  316. } else {
  317. binding.messagesRingtone.setTextColor(
  318. ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
  319. )
  320. binding.messagesRingtone.text = resources!!.getString(R.string.nc_common_disabled)
  321. }
  322. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  323. binding.settingsCallSound.setOnClickListener {
  324. val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
  325. intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
  326. intent.putExtra(
  327. Settings.EXTRA_CHANNEL_ID,
  328. NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
  329. )
  330. startActivity(intent)
  331. }
  332. binding.settingsMessageSound.setOnClickListener {
  333. val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
  334. intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
  335. intent.putExtra(
  336. Settings.EXTRA_CHANNEL_ID,
  337. NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name
  338. )
  339. startActivity(intent)
  340. }
  341. } else {
  342. Log.w(TAG, "setupSoundSettings currently not supported for versions < Build.VERSION_CODES.O")
  343. }
  344. }
  345. private fun setTroubleshootingClickListenersIfNecessary() {
  346. fun click() {
  347. val dialogBuilder = MaterialAlertDialogBuilder(this)
  348. .setTitle(R.string.nc_notifications_troubleshooting_dialog_title)
  349. .setMessage(R.string.nc_notifications_troubleshooting_dialog_text)
  350. .setNegativeButton(R.string.nc_diagnose_dialog_open_checklist) { _, _ ->
  351. startActivity(
  352. Intent(
  353. Intent.ACTION_VIEW,
  354. Uri.parse(resources.getString(R.string.notification_checklist_url))
  355. )
  356. )
  357. }
  358. .setPositiveButton(R.string.nc_diagnose_dialog_open_dontkillmyapp_website) { _, _ ->
  359. startActivity(
  360. Intent(
  361. Intent.ACTION_VIEW,
  362. Uri.parse(resources.getString(R.string.dontkillmyapp_url))
  363. )
  364. )
  365. }
  366. .setNeutralButton(R.string.nc_diagnose_dialog_open_diagnose) { _, _ ->
  367. val intent = Intent(context, DiagnoseActivity::class.java)
  368. startActivity(intent)
  369. }
  370. viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
  371. val dialog = dialogBuilder.show()
  372. viewThemeUtils.platform.colorTextButtons(
  373. dialog.getButton(AlertDialog.BUTTON_POSITIVE),
  374. dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
  375. dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
  376. )
  377. }
  378. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
  379. if (platformPermissionUtil.isPostNotificationsPermissionGranted() &&
  380. PowerManagerUtils().isIgnoringBatteryOptimizations()
  381. ) {
  382. binding.settingsNotificationsPermissionWrapper.setOnClickListener { click() }
  383. binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
  384. }
  385. } else if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
  386. binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
  387. }
  388. }
  389. private fun setupSourceCodeUrl() {
  390. if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_source_code_url))) {
  391. binding.settingsSourceCode.setOnClickListener {
  392. startActivity(
  393. Intent(
  394. Intent.ACTION_VIEW,
  395. Uri.parse(resources!!.getString(R.string.nc_source_code_url))
  396. )
  397. )
  398. }
  399. } else {
  400. binding.settingsSourceCode.visibility = View.GONE
  401. }
  402. }
  403. private fun setupDiagnose() {
  404. binding.diagnoseWrapper.setOnClickListener {
  405. val intent = Intent(context, DiagnoseActivity::class.java)
  406. startActivity(intent)
  407. }
  408. }
  409. private fun setupPrivacyUrl() {
  410. if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_privacy_url))) {
  411. binding.settingsPrivacy.setOnClickListener {
  412. startActivity(
  413. Intent(
  414. Intent.ACTION_VIEW,
  415. Uri.parse(resources!!.getString(R.string.nc_privacy_url))
  416. )
  417. )
  418. }
  419. } else {
  420. binding.settingsPrivacy.visibility = View.GONE
  421. }
  422. }
  423. private fun setupLicenceSetting() {
  424. if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) {
  425. binding.settingsLicence.setOnClickListener {
  426. startActivity(
  427. Intent(
  428. Intent.ACTION_VIEW,
  429. Uri.parse(resources!!.getString(R.string.nc_gpl3_url))
  430. )
  431. )
  432. }
  433. } else {
  434. binding.settingsLicence.visibility = View.GONE
  435. }
  436. }
  437. private fun setupClientCertView() {
  438. var host: String? = null
  439. var port = -1
  440. val uri: URI
  441. try {
  442. uri = URI(currentUser!!.baseUrl!!)
  443. host = uri.host
  444. port = uri.port
  445. Log.d(TAG, "uri is $uri")
  446. } catch (e: URISyntaxException) {
  447. Log.e(TAG, "Failed to create uri")
  448. }
  449. binding.settingsClientCert.setOnClickListener {
  450. KeyChain.choosePrivateKeyAlias(
  451. this,
  452. { alias: String? ->
  453. var finalAlias: String? = alias
  454. runOnUiThread {
  455. if (finalAlias != null) {
  456. binding.settingsClientCertTitle.setText(R.string.nc_client_cert_change)
  457. } else {
  458. binding.settingsClientCertTitle.setText(R.string.nc_client_cert_setup)
  459. }
  460. }
  461. if (finalAlias == null) {
  462. finalAlias = ""
  463. }
  464. Log.d(TAG, "host: $host and port: $port")
  465. currentUser!!.clientCertificate = finalAlias
  466. userManager.updateOrCreateUser(currentUser!!)
  467. },
  468. arrayOf("RSA", "EC"),
  469. null,
  470. host,
  471. port,
  472. currentUser!!.clientCertificate
  473. )
  474. }
  475. }
  476. @OptIn(ExperimentalCoroutinesApi::class)
  477. private fun registerChangeListeners() {
  478. val appPreferences = AppPreferencesImpl(context)
  479. proxyTypeFlow = appPreferences.readString(AppPreferencesImpl.PROXY_TYPE)
  480. proxyCredentialFlow = appPreferences.readBoolean(AppPreferencesImpl.PROXY_CRED)
  481. screenSecurityFlow = appPreferences.readBoolean(AppPreferencesImpl.SCREEN_SECURITY)
  482. screenLockFlow = appPreferences.readBoolean(AppPreferencesImpl.SCREEN_LOCK)
  483. screenLockTimeoutFlow = appPreferences.readString(AppPreferencesImpl.SCREEN_LOCK_TIMEOUT)
  484. val themeKey = context.resources.getString(R.string.nc_settings_theme_key)
  485. themeFlow = appPreferences.readString(themeKey)
  486. val privacyKey = context.resources.getString(R.string.nc_settings_read_privacy_key)
  487. readPrivacyFlow = appPreferences.readBoolean(privacyKey)
  488. typingStatusFlow = appPreferences.readBoolean(AppPreferencesImpl.TYPING_STATUS)
  489. phoneBookIntegrationFlow = appPreferences.readBoolean(AppPreferencesImpl.PHONE_BOOK_INTEGRATION)
  490. var pos = resources.getStringArray(R.array.screen_lock_timeout_entry_values).indexOf(
  491. appPreferences.screenLockTimeout
  492. )
  493. binding.settingsScreenLockTimeoutLayoutDropdown.setText(
  494. resources.getStringArray(R.array.screen_lock_timeout_descriptions)[pos]
  495. )
  496. binding.settingsScreenLockTimeoutLayoutDropdown.setSimpleItems(R.array.screen_lock_timeout_descriptions)
  497. binding.settingsScreenLockTimeoutLayoutDropdown.setOnItemClickListener { _, _, position, _ ->
  498. val entryVal: String = resources.getStringArray(R.array.screen_lock_timeout_entry_values)[position]
  499. appPreferences.screenLockTimeout = entryVal
  500. SecurityUtils.createKey(entryVal)
  501. }
  502. pos = resources.getStringArray(R.array.theme_entry_values).indexOf(appPreferences.theme)
  503. binding.settingsTheme.setText(resources.getStringArray(R.array.theme_descriptions)[pos])
  504. binding.settingsTheme.setSimpleItems(R.array.theme_descriptions)
  505. binding.settingsTheme.setOnItemClickListener { _, _, position, _ ->
  506. val entryVal: String = resources.getStringArray(R.array.theme_entry_values)[position]
  507. appPreferences.theme = entryVal
  508. }
  509. observeProxyType()
  510. observeProxyCredential()
  511. observeScreenSecurity()
  512. observeScreenLock()
  513. observeTheme()
  514. observeReadPrivacy()
  515. observeTypingStatus()
  516. }
  517. fun sendLogs() {
  518. if (resources!!.getBoolean(R.bool.nc_is_debug)) {
  519. sendMailWithAttachment((context))
  520. }
  521. }
  522. private fun showRemoveAccountWarning() {
  523. binding.messageText.context?.let {
  524. val materialAlertDialogBuilder = MaterialAlertDialogBuilder(it)
  525. .setTitle(R.string.nc_settings_remove_account)
  526. .setMessage(R.string.nc_settings_remove_confirmation)
  527. .setPositiveButton(R.string.nc_settings_remove) { _, _ ->
  528. removeCurrentAccount()
  529. }
  530. .setNegativeButton(R.string.nc_cancel) { _, _ ->
  531. // unused atm
  532. }
  533. viewThemeUtils.dialog.colorMaterialAlertDialogBackground(
  534. it,
  535. materialAlertDialogBuilder
  536. )
  537. val dialog = materialAlertDialogBuilder.show()
  538. viewThemeUtils.platform.colorTextButtons(
  539. dialog.getButton(AlertDialog.BUTTON_POSITIVE),
  540. dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
  541. )
  542. }
  543. }
  544. @SuppressLint("CheckResult", "StringFormatInvalid")
  545. private fun removeCurrentAccount() {
  546. userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
  547. val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
  548. WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
  549. WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
  550. .observeForever { workInfo: WorkInfo ->
  551. when (workInfo.state) {
  552. WorkInfo.State.SUCCEEDED -> {
  553. val text = String.format(
  554. context.resources.getString(R.string.nc_deleted_user),
  555. currentUser!!.displayName
  556. )
  557. Toast.makeText(
  558. context,
  559. text,
  560. Toast.LENGTH_LONG
  561. ).show()
  562. restartApp()
  563. }
  564. WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
  565. Toast.makeText(
  566. context,
  567. context.resources.getString(R.string.nc_common_error_sorry),
  568. Toast.LENGTH_LONG
  569. ).show()
  570. Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
  571. restartApp()
  572. }
  573. else -> {}
  574. }
  575. }
  576. }
  577. private fun restartApp() {
  578. val intent = Intent(context, MainActivity::class.java)
  579. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  580. startActivity(intent)
  581. }
  582. private fun getRingtoneName(context: Context, ringtoneUri: Uri?): String {
  583. return if (ringtoneUri == null) {
  584. resources!!.getString(R.string.nc_settings_no_ringtone)
  585. } else if ((NotificationUtils.DEFAULT_CALL_RINGTONE_URI == ringtoneUri.toString()) ||
  586. (NotificationUtils.DEFAULT_MESSAGE_RINGTONE_URI == ringtoneUri.toString())
  587. ) {
  588. resources!!.getString(R.string.nc_settings_default_ringtone)
  589. } else {
  590. val r = RingtoneManager.getRingtone(context, ringtoneUri)
  591. r.getTitle(context)
  592. }
  593. }
  594. private fun themeSwitchPreferences() {
  595. binding.run {
  596. listOf(
  597. settingsScreenLockSwitch,
  598. settingsScreenSecuritySwitch,
  599. settingsIncognitoKeyboardSwitch,
  600. settingsPhoneBookIntegrationSwitch,
  601. settingsReadPrivacySwitch,
  602. settingsTypingStatusSwitch,
  603. settingsProxyUseCredentialsSwitch
  604. ).forEach(viewThemeUtils.talk::colorSwitch)
  605. }
  606. }
  607. private fun themeTitles() {
  608. binding.run {
  609. listOf(
  610. settingsNotificationsTitle,
  611. settingsAboutTitle,
  612. settingsAdvancedTitle,
  613. settingsAppearanceTitle,
  614. settingsPrivacyTitle
  615. ).forEach(viewThemeUtils.platform::colorTextView)
  616. }
  617. }
  618. private fun setupProxyTypeSettings() {
  619. if (appPreferences.proxyType == null) {
  620. appPreferences.proxyType = resources.getString(R.string.nc_no_proxy)
  621. }
  622. binding.settingsProxyChoice.setText(appPreferences.proxyType)
  623. binding.settingsProxyChoice.setSimpleItems(R.array.proxy_type_descriptions)
  624. binding.settingsProxyChoice.setOnItemClickListener { _, _, position, _ ->
  625. val entryVal = resources.getStringArray(R.array.proxy_type_descriptions)[position]
  626. appPreferences.proxyType = entryVal
  627. }
  628. binding.settingsProxyHostEdit.setText(appPreferences.proxyHost)
  629. binding.settingsProxyHostEdit.setOnFocusChangeListener { _, hasFocus ->
  630. if (!hasFocus) {
  631. appPreferences.proxyHost = binding.settingsProxyHostEdit.text.toString()
  632. }
  633. }
  634. binding.settingsProxyPortEdit.setText(appPreferences.proxyPort)
  635. binding.settingsProxyPortEdit.setOnFocusChangeListener { _, hasFocus ->
  636. if (!hasFocus) {
  637. appPreferences.proxyPort = binding.settingsProxyPortEdit.text.toString()
  638. }
  639. }
  640. binding.settingsProxyUsernameEdit.setText(appPreferences.proxyUsername)
  641. binding.settingsProxyUsernameEdit.setOnFocusChangeListener { _, hasFocus ->
  642. if (!hasFocus) {
  643. appPreferences.proxyUsername = binding.settingsProxyUsernameEdit.text.toString()
  644. }
  645. }
  646. binding.settingsProxyPasswordEdit.setText(appPreferences.proxyPassword)
  647. binding.settingsProxyPasswordEdit.setOnFocusChangeListener { _, hasFocus ->
  648. if (!hasFocus) {
  649. appPreferences.proxyPassword = binding.settingsProxyPasswordEdit.text.toString()
  650. }
  651. }
  652. if (((context.resources.getString(R.string.nc_no_proxy)) == appPreferences.proxyType) ||
  653. appPreferences.proxyType == null
  654. ) {
  655. hideProxySettings()
  656. } else {
  657. showProxySettings()
  658. }
  659. }
  660. private fun setupProxyCredentialSettings() {
  661. if (appPreferences.proxyCredentials) {
  662. showProxyCredentials()
  663. } else {
  664. hideProxyCredentials()
  665. }
  666. }
  667. private fun setupMessageView() {
  668. if (ApplicationWideMessageHolder.getInstance().messageType != null) {
  669. when (ApplicationWideMessageHolder.getInstance().messageType) {
  670. ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED -> {
  671. binding.messageText.let {
  672. viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
  673. it.text = resources!!.getString(R.string.nc_settings_account_updated)
  674. binding.messageText.visibility = View.VISIBLE
  675. }
  676. }
  677. ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> {
  678. binding.messageText.let {
  679. it.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
  680. it.text = resources!!.getString(R.string.nc_settings_wrong_account)
  681. binding.messageText.visibility = View.VISIBLE
  682. viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
  683. it.text = resources!!.getString(R.string.nc_Server_account_imported)
  684. binding.messageText.visibility = View.VISIBLE
  685. }
  686. }
  687. ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED -> {
  688. binding.messageText.let {
  689. viewThemeUtils.platform.colorTextView(it, ColorRole.PRIMARY)
  690. it.text = resources!!.getString(R.string.nc_Server_account_imported)
  691. binding.messageText.visibility = View.VISIBLE
  692. }
  693. }
  694. ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> {
  695. binding.messageText.let {
  696. it.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
  697. it.text = resources!!.getString(R.string.nc_server_failed_to_import_account)
  698. binding.messageText.visibility = View.VISIBLE
  699. }
  700. }
  701. else -> binding.messageText.visibility = View.GONE
  702. }
  703. ApplicationWideMessageHolder.getInstance().messageType = null
  704. binding.messageText.animate()
  705. ?.translationY(0f)
  706. ?.alpha(0.0f)
  707. ?.setDuration(DURATION)
  708. ?.setStartDelay(START_DELAY)
  709. ?.setListener(object : AnimatorListenerAdapter() {
  710. override fun onAnimationEnd(animation: Animator) {
  711. super.onAnimationEnd(animation)
  712. binding.messageText.visibility = View.GONE
  713. }
  714. })
  715. } else {
  716. binding.messageText.visibility = View.GONE
  717. }
  718. }
  719. private fun setupProfileQueryDisposable() {
  720. profileQueryDisposable = ncApi.getUserProfile(
  721. credentials,
  722. ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
  723. )
  724. .subscribeOn(Schedulers.io())
  725. .observeOn(AndroidSchedulers.mainThread())
  726. .subscribe(
  727. { userProfileOverall: UserProfileOverall ->
  728. var displayName: String? = null
  729. if (!TextUtils.isEmpty(
  730. userProfileOverall.ocs!!.data!!.displayName
  731. )
  732. ) {
  733. displayName = userProfileOverall.ocs!!.data!!.displayName
  734. } else if (!TextUtils.isEmpty(
  735. userProfileOverall.ocs!!.data!!.displayNameAlt
  736. )
  737. ) {
  738. displayName = userProfileOverall.ocs!!.data!!.displayNameAlt
  739. }
  740. if ((!TextUtils.isEmpty(displayName) && !(displayName == currentUser!!.displayName))) {
  741. currentUser!!.displayName = displayName
  742. userManager.updateOrCreateUser(currentUser!!)
  743. binding.nameText.text = currentUser!!.displayName
  744. }
  745. },
  746. { dispose(profileQueryDisposable) },
  747. { dispose(profileQueryDisposable) }
  748. )
  749. }
  750. private fun setupServerAgeWarning() {
  751. when {
  752. CapabilitiesUtil.isServerEOL(currentUser!!.serverVersion?.major) -> {
  753. binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context), R.color.nc_darkRed))
  754. binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
  755. binding.serverAgeWarningIcon.setColorFilter(
  756. ContextCompat.getColor((context), R.color.nc_darkRed),
  757. PorterDuff.Mode.SRC_IN
  758. )
  759. }
  760. CapabilitiesUtil.isServerAlmostEOL(currentUser!!.serverVersion?.major) -> {
  761. binding.serverAgeWarningText.setTextColor(
  762. ContextCompat.getColor((context), R.color.nc_darkYellow)
  763. )
  764. binding.serverAgeWarningText.setText(R.string.nc_settings_server_almost_eol)
  765. binding.serverAgeWarningIcon.setColorFilter(
  766. ContextCompat.getColor((context), R.color.nc_darkYellow),
  767. PorterDuff.Mode.SRC_IN
  768. )
  769. }
  770. else -> {
  771. binding.serverAgeWarningTextCard.visibility = View.GONE
  772. }
  773. }
  774. }
  775. private fun setupCheckables() {
  776. binding.settingsScreenSecuritySwitch.isChecked = appPreferences.isScreenSecured
  777. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  778. binding.settingsIncognitoKeyboardSwitch.isChecked = appPreferences.isKeyboardIncognito
  779. } else {
  780. binding.settingsIncognitoKeyboardSwitch.visibility = View.GONE
  781. }
  782. if (CapabilitiesUtil.isReadStatusAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
  783. binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtil.isReadStatusPrivate(currentUser!!)
  784. } else {
  785. binding.settingsReadPrivacy.visibility = View.GONE
  786. }
  787. setupTypingStatusSetting()
  788. binding.settingsPhoneBookIntegrationSwitch.isChecked = appPreferences.isPhoneBookIntegrationEnabled
  789. binding.settingsProxyUseCredentialsSwitch.isChecked = appPreferences.proxyCredentials
  790. binding.settingsProxyUseCredentials.setOnClickListener {
  791. val isChecked = binding.settingsProxyUseCredentialsSwitch.isChecked
  792. binding.settingsProxyUseCredentialsSwitch.isChecked = !isChecked
  793. appPreferences.setProxyNeedsCredentials(!isChecked)
  794. }
  795. binding.settingsScreenLockSwitch.isChecked = appPreferences.isScreenLocked
  796. binding.settingsScreenLock.setOnClickListener {
  797. val isChecked = binding.settingsScreenLockSwitch.isChecked
  798. binding.settingsScreenLockSwitch.isChecked = !isChecked
  799. appPreferences.setScreenLock(!isChecked)
  800. }
  801. binding.settingsReadPrivacy.setOnClickListener {
  802. val isChecked = binding.settingsReadPrivacySwitch.isChecked
  803. binding.settingsReadPrivacySwitch.isChecked = !isChecked
  804. appPreferences.setReadPrivacy(!isChecked)
  805. }
  806. binding.settingsIncognitoKeyboard.setOnClickListener {
  807. val isChecked = binding.settingsIncognitoKeyboardSwitch.isChecked
  808. binding.settingsIncognitoKeyboardSwitch.isChecked = !isChecked
  809. appPreferences.setIncognitoKeyboard(!isChecked)
  810. }
  811. binding.settingsPhoneBookIntegration.setOnClickListener {
  812. val isChecked = binding.settingsPhoneBookIntegrationSwitch.isChecked
  813. binding.settingsPhoneBookIntegrationSwitch.isChecked = !isChecked
  814. appPreferences.setPhoneBookIntegration(!isChecked)
  815. if (!isChecked) {
  816. if (checkPermission(this@SettingsActivity, (context))) {
  817. checkForPhoneNumber()
  818. }
  819. } else {
  820. deleteAll()
  821. }
  822. }
  823. binding.settingsScreenSecurity.setOnClickListener {
  824. val isChecked = binding.settingsScreenSecuritySwitch.isChecked
  825. binding.settingsScreenSecuritySwitch.isChecked = !isChecked
  826. appPreferences.setScreenSecurity(!isChecked)
  827. }
  828. binding.settingsTypingStatus.setOnClickListener {
  829. val isChecked = binding.settingsTypingStatusSwitch.isChecked
  830. binding.settingsTypingStatusSwitch.isChecked = !isChecked
  831. appPreferences.setTypingStatus(!isChecked)
  832. }
  833. }
  834. private fun setupTypingStatusSetting() {
  835. if (currentUser!!.externalSignalingServer?.externalSignalingServer?.isNotEmpty() == true) {
  836. binding.settingsTypingStatusOnlyWithHpb.visibility = View.GONE
  837. Log.i(TAG, "Typing Status Available: ${CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)}")
  838. if (CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)) {
  839. binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtil.isTypingStatusPrivate(currentUser!!)
  840. } else {
  841. binding.settingsTypingStatus.visibility = View.GONE
  842. }
  843. } else {
  844. Log.i(TAG, "Typing Status not Available")
  845. binding.settingsTypingStatusSwitch.isChecked = false
  846. binding.settingsTypingStatusOnlyWithHpb.visibility = View.VISIBLE
  847. binding.settingsTypingStatus.isEnabled = false
  848. binding.settingsTypingStatusOnlyWithHpb.alpha = DISABLED_ALPHA
  849. binding.settingsTypingStatus.alpha = DISABLED_ALPHA
  850. }
  851. }
  852. private fun setupScreenLockSetting() {
  853. val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
  854. if (keyguardManager.isKeyguardSecure) {
  855. binding.settingsScreenLock.isEnabled = true
  856. binding.settingsScreenLockTimeout.isEnabled = true
  857. binding.settingsScreenLockSwitch.isChecked = appPreferences.isScreenLocked
  858. binding.settingsScreenLockTimeoutLayoutDropdown.isEnabled = appPreferences.isScreenLocked
  859. if (appPreferences.isScreenLocked) {
  860. binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
  861. } else {
  862. binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
  863. }
  864. binding.settingsScreenLock.alpha = ENABLED_ALPHA
  865. } else {
  866. binding.settingsScreenLock.isEnabled = false
  867. binding.settingsScreenLockTimeoutLayoutDropdown.isEnabled = false
  868. appPreferences.removeScreenLock()
  869. appPreferences.removeScreenLockTimeout()
  870. binding.settingsScreenLockSwitch.isChecked = false
  871. binding.settingsScreenLock.alpha = DISABLED_ALPHA
  872. binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
  873. }
  874. }
  875. public override fun onDestroy() {
  876. // appPreferences.unregisterProxyTypeListener(proxyTypeChangeListener)
  877. // appPreferences.unregisterProxyCredentialsListener(proxyCredentialsChangeListener)
  878. // appPreferences.unregisterScreenSecurityListener(screenSecurityChangeListener)
  879. // appPreferences.unregisterScreenLockListener(screenLockChangeListener)
  880. // appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener)
  881. // appPreferences.unregisterThemeChangeListener(themeChangeListener)
  882. // appPreferences.unregisterReadPrivacyChangeListener(readPrivacyChangeListener)
  883. // appPreferences.unregisterTypingStatusChangeListener(typingStatusChangeListener)
  884. // appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener)
  885. super.onDestroy()
  886. }
  887. private fun hideProxySettings() {
  888. appPreferences.removeProxyHost()
  889. appPreferences.removeProxyPort()
  890. appPreferences.removeProxyCredentials()
  891. appPreferences.removeProxyUsername()
  892. appPreferences.removeProxyPassword()
  893. binding.settingsProxyHostLayout.visibility = View.GONE
  894. binding.settingsProxyPortLayout.visibility = View.GONE
  895. binding.settingsProxyUseCredentials.visibility = View.GONE
  896. hideProxyCredentials()
  897. }
  898. private fun showProxySettings() {
  899. binding.settingsProxyHostLayout.visibility =
  900. View.VISIBLE
  901. binding.settingsProxyPortLayout.visibility =
  902. View.VISIBLE
  903. binding.settingsProxyUseCredentials.visibility =
  904. View.VISIBLE
  905. if (binding.settingsProxyUseCredentialsSwitch.isChecked) showProxyCredentials()
  906. }
  907. private fun showProxyCredentials() {
  908. binding.settingsProxyUsernameLayout.visibility =
  909. View.VISIBLE
  910. binding.settingsProxyPasswordLayout.visibility =
  911. View.VISIBLE
  912. }
  913. private fun hideProxyCredentials() {
  914. appPreferences.removeProxyUsername()
  915. appPreferences.removeProxyPassword()
  916. binding.settingsProxyUsernameLayout.visibility = View.GONE
  917. binding.settingsProxyPasswordLayout.visibility = View.GONE
  918. }
  919. private fun dispose(disposable: Disposable?) {
  920. if (disposable != null && !disposable.isDisposed) {
  921. disposable.dispose()
  922. } else if (disposable == null) {
  923. disposeProfileQueryDisposable()
  924. disposeDbQueryDisposable()
  925. }
  926. }
  927. private fun disposeDbQueryDisposable() {
  928. if (dbQueryDisposable != null && !dbQueryDisposable!!.isDisposed) {
  929. dbQueryDisposable!!.dispose()
  930. dbQueryDisposable = null
  931. } else if (dbQueryDisposable != null) {
  932. dbQueryDisposable = null
  933. }
  934. }
  935. private fun disposeProfileQueryDisposable() {
  936. if (profileQueryDisposable != null && !profileQueryDisposable!!.isDisposed) {
  937. profileQueryDisposable!!.dispose()
  938. profileQueryDisposable = null
  939. } else if (profileQueryDisposable != null) {
  940. profileQueryDisposable = null
  941. }
  942. }
  943. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
  944. super.onRequestPermissionsResult(requestCode, permissions, grantResults)
  945. when (requestCode) {
  946. ContactAddressBookWorker.REQUEST_PERMISSION -> {
  947. if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  948. WorkManager
  949. .getInstance(this)
  950. .enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java).build())
  951. checkForPhoneNumber()
  952. } else {
  953. appPreferences.setPhoneBookIntegration(false)
  954. binding.settingsPhoneBookIntegrationSwitch.isChecked = appPreferences.isPhoneBookIntegrationEnabled
  955. Snackbar.make(
  956. binding.root,
  957. context.resources.getString(R.string.no_phone_book_integration_due_to_permissions),
  958. Snackbar.LENGTH_LONG
  959. ).show()
  960. }
  961. }
  962. ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION -> {
  963. if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_DENIED) {
  964. Snackbar.make(
  965. binding.root,
  966. context.resources.getString(R.string.nc_settings_notifications_declined_hint),
  967. Snackbar.LENGTH_LONG
  968. ).show()
  969. Log.d(
  970. TAG,
  971. "Notification permission is denied. Either because user denied it when being asked. " +
  972. "Or permission is already denied and android decided to not offer the dialog."
  973. )
  974. }
  975. }
  976. }
  977. }
  978. private fun observeScreenLock() {
  979. CoroutineScope(Dispatchers.Main).launch {
  980. var state = appPreferences.isScreenLocked
  981. screenLockFlow.collect { newBoolean ->
  982. if (newBoolean != state) {
  983. state = newBoolean
  984. binding.settingsScreenLockTimeout.isEnabled = newBoolean
  985. if (newBoolean) {
  986. binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA
  987. } else {
  988. binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA
  989. }
  990. }
  991. }
  992. }
  993. }
  994. private fun observeScreenSecurity() {
  995. CoroutineScope(Dispatchers.Main).launch {
  996. var state = appPreferences.isScreenSecured
  997. screenSecurityFlow.collect { newBoolean ->
  998. if (newBoolean != state) {
  999. state = newBoolean
  1000. if (newBoolean) {
  1001. window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
  1002. } else {
  1003. window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
  1004. }
  1005. }
  1006. }
  1007. }
  1008. }
  1009. private fun observeProxyCredential() {
  1010. CoroutineScope(Dispatchers.Main).launch {
  1011. var state = appPreferences.proxyCredentials
  1012. proxyCredentialFlow.collect { newBoolean ->
  1013. if (newBoolean != state) {
  1014. state = newBoolean
  1015. if (newBoolean) {
  1016. showProxyCredentials()
  1017. } else {
  1018. hideProxyCredentials()
  1019. }
  1020. }
  1021. }
  1022. }
  1023. }
  1024. private fun observeProxyType() {
  1025. CoroutineScope(Dispatchers.Main).launch {
  1026. var state = appPreferences.proxyType
  1027. proxyTypeFlow.collect { newString ->
  1028. if (newString != state) {
  1029. state = newString
  1030. if (((context.resources.getString(R.string.nc_no_proxy)) == newString) || newString.isEmpty()) {
  1031. hideProxySettings()
  1032. } else {
  1033. when (newString) {
  1034. "HTTP" -> {
  1035. binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_http_value))
  1036. appPreferences.proxyPort = getString(R.string.nc_settings_http_value)
  1037. }
  1038. "DIRECT" -> {
  1039. binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_direct_value))
  1040. appPreferences.proxyPort = getString(R.string.nc_settings_direct_value)
  1041. }
  1042. "SOCKS" -> {
  1043. binding.settingsProxyPortEdit.setText(getString(R.string.nc_settings_socks_value))
  1044. appPreferences.proxyPort = getString(R.string.nc_settings_socks_value)
  1045. }
  1046. else -> {
  1047. }
  1048. }
  1049. showProxySettings()
  1050. }
  1051. }
  1052. }
  1053. }
  1054. }
  1055. private fun observeTheme() {
  1056. CoroutineScope(Dispatchers.Main).launch {
  1057. var state = appPreferences.theme
  1058. themeFlow.collect { newString ->
  1059. if (newString != state) {
  1060. state = newString
  1061. setAppTheme(newString)
  1062. }
  1063. }
  1064. }
  1065. }
  1066. private fun checkForPhoneNumber() {
  1067. ncApi.getUserData(
  1068. ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
  1069. ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
  1070. ).subscribeOn(Schedulers.io())
  1071. .observeOn(AndroidSchedulers.mainThread())
  1072. .subscribe(object : Observer<UserProfileOverall> {
  1073. override fun onSubscribe(d: Disposable) {
  1074. // unused atm
  1075. }
  1076. override fun onNext(userProfileOverall: UserProfileOverall) {
  1077. if (userProfileOverall.ocs!!.data!!.phone?.isEmpty() == true) {
  1078. askForPhoneNumber()
  1079. } else {
  1080. Log.d(TAG, "phone number already set")
  1081. }
  1082. }
  1083. override fun onError(e: Throwable) {
  1084. // unused atm
  1085. }
  1086. override fun onComplete() {
  1087. // unused atm
  1088. }
  1089. })
  1090. }
  1091. private fun askForPhoneNumber() {
  1092. val dialog = SetPhoneNumberDialogFragment.newInstance()
  1093. dialog.show(supportFragmentManager, SetPhoneNumberDialogFragment.TAG)
  1094. }
  1095. override fun onSubmitClick(textInputLayout: TextInputLayout, dialog: DialogInterface) {
  1096. setPhoneNumber(textInputLayout, dialog)
  1097. }
  1098. private fun setPhoneNumber(textInputLayout: TextInputLayout, dialog: DialogInterface) {
  1099. val phoneNumber = textInputLayout.editText!!.text.toString()
  1100. ncApi.setUserData(
  1101. ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
  1102. ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
  1103. "phone",
  1104. phoneNumber
  1105. ).subscribeOn(Schedulers.io())
  1106. .observeOn(AndroidSchedulers.mainThread())
  1107. .subscribe(object : Observer<GenericOverall> {
  1108. override fun onSubscribe(d: Disposable) {
  1109. // unused atm
  1110. }
  1111. override fun onNext(genericOverall: GenericOverall) {
  1112. when (val statusCode = genericOverall.ocs?.meta?.statusCode) {
  1113. HTTP_CODE_OK -> {
  1114. dialog.dismiss()
  1115. Snackbar.make(
  1116. binding.root,
  1117. getString(
  1118. R.string.nc_settings_phone_book_integration_phone_number_dialog_success
  1119. ),
  1120. Snackbar.LENGTH_LONG
  1121. ).show()
  1122. }
  1123. else -> {
  1124. textInputLayout.helperText = getString(
  1125. R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
  1126. )
  1127. Log.d(TAG, "failed to set phoneNumber. statusCode=$statusCode")
  1128. }
  1129. }
  1130. }
  1131. override fun onError(e: Throwable) {
  1132. textInputLayout.helperText = getString(
  1133. R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
  1134. )
  1135. Log.e(SetPhoneNumberDialogFragment.TAG, "setPhoneNumber error", e)
  1136. }
  1137. override fun onComplete() {
  1138. // unused atm
  1139. }
  1140. })
  1141. }
  1142. private fun observeReadPrivacy() {
  1143. CoroutineScope(Dispatchers.Main).launch {
  1144. var state = appPreferences.readPrivacy
  1145. readPrivacyFlow.collect { newBoolean ->
  1146. if (state != newBoolean) {
  1147. state = newBoolean
  1148. val booleanValue = if (newBoolean) "0" else "1"
  1149. val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
  1150. try {
  1151. ncApiCoroutines.setReadStatusPrivacy(
  1152. ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
  1153. ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
  1154. json.toRequestBody("application/json".toMediaTypeOrNull())
  1155. )
  1156. Log.i(TAG, "reading status set")
  1157. } catch (e: Exception) {
  1158. appPreferences.setReadPrivacy(!newBoolean)
  1159. binding.settingsReadPrivacySwitch.isChecked = !newBoolean
  1160. }
  1161. }
  1162. }
  1163. }
  1164. }
  1165. private fun observeTypingStatus() {
  1166. CoroutineScope(Dispatchers.Main).launch {
  1167. var state = appPreferences.typingStatus
  1168. typingStatusFlow.collect { newBoolean ->
  1169. if (state != newBoolean) {
  1170. state = newBoolean
  1171. val booleanValue = if (newBoolean) "0" else "1"
  1172. val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
  1173. try {
  1174. ncApiCoroutines.setTypingStatusPrivacy(
  1175. ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
  1176. ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
  1177. json.toRequestBody("application/json".toMediaTypeOrNull())
  1178. )
  1179. loadCapabilitiesAndUpdateSettings()
  1180. Log.i(TAG, "typing status set")
  1181. } catch (e: Exception) {
  1182. appPreferences.typingStatus = !newBoolean
  1183. binding.settingsTypingStatusSwitch.isChecked = !newBoolean
  1184. }
  1185. }
  1186. }
  1187. }
  1188. }
  1189. companion object {
  1190. private val TAG = SettingsActivity::class.java.simpleName
  1191. private const val DURATION: Long = 2500
  1192. private const val START_DELAY: Long = 5000
  1193. private const val DISABLED_ALPHA: Float = 0.38f
  1194. private const val ENABLED_ALPHA: Float = 1.0f
  1195. const val HTTP_CODE_OK: Int = 200
  1196. }
  1197. }