From 07ea11f2167f63ca9fd95e67899ea7f9d9990844 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Fri, 24 Apr 2020 14:19:04 +0200 Subject: [PATCH] Fix #673, #793, #502 Signed-off-by: Mario Danic --- .../application/NextcloudTalkApplication.kt | 3 +- .../RingtoneSelectionController.kt | 7 +- .../talk/controllers/SettingsController.kt | 852 ------------------ .../controllers/SwitchAccountController.kt | 266 ------ .../talk/controllers/base/BaseController.kt | 15 - .../repository/offline/UsersRepositoryImpl.kt | 15 + .../repository/offline/UsersRepository.kt | 3 + .../talk/newarch/features/chat/ChatView.kt | 2 +- .../ConversationsListView.kt | 10 +- .../settingsflow/di/module/SettingsModule.kt | 22 + .../looknfeel/SettingsLookNFeelView.kt | 149 +++ .../privacy/SettingsPrivacyView.kt | 178 ++++ .../settings/SettingsPresenter.kt | 88 ++ .../settingsflow/settings/SettingsUtils.kt | 6 + .../settingsflow/settings/SettingsView.kt | 255 ++++++ .../settings/SettingsViewFooterSource.kt | 59 ++ .../settings/SettingsViewModel.kt | 85 ++ .../settings/SettingsViewModelFactory.kt | 45 + .../settings/SettingsViewSource.kt | 47 + .../settings/SettingsViewUserPresenter.kt | 23 + .../local/converters/UserStatusConverter.kt | 6 +- .../talk/newarch/local/dao/UsersDao.kt | 25 +- .../newarch/local/models/other/UserStatus.kt | 6 +- .../nextcloud/talk/newarch/utils/Images.kt | 2 +- .../utils/preferences/AppPreferences.java | 16 + .../res/drawable/ic_baseline_more_vert_24.xml | 5 + .../main/res/layout/controller_settings.xml | 118 +-- .../res/layout/settings_looknfeel_view.xml | 79 ++ .../main/res/layout/settings_privacy_view.xml | 140 +++ app/src/main/res/layout/settings_view.xml | 134 +++ app/src/main/res/layout/user_item.xml | 50 + app/src/main/res/values/strings.xml | 5 + 32 files changed, 1448 insertions(+), 1268 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/di/module/SettingsModule.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/looknfeel/SettingsLookNFeelView.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/privacy/SettingsPrivacyView.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsPresenter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsUtils.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsView.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewFooterSource.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModel.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModelFactory.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewSource.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewUserPresenter.kt create mode 100644 app/src/main/res/drawable/ic_baseline_more_vert_24.xml create mode 100644 app/src/main/res/layout/settings_looknfeel_view.xml create mode 100644 app/src/main/res/layout/settings_privacy_view.xml create mode 100644 app/src/main/res/layout/settings_view.xml create mode 100644 app/src/main/res/layout/user_item.xml diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 41ef27f91..497da93e5 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -51,6 +51,7 @@ import com.nextcloud.talk.newarch.domain.di.module.UseCasesModule import com.nextcloud.talk.newarch.features.account.di.module.AccountModule import com.nextcloud.talk.newarch.features.contactsflow.di.module.ContactsFlowModule import com.nextcloud.talk.newarch.features.conversationsList.di.module.ConversationsListModule +import com.nextcloud.talk.newarch.features.settingsflow.di.module.SettingsModule import com.nextcloud.talk.newarch.local.dao.UsersDao import com.nextcloud.talk.newarch.local.models.User import com.nextcloud.talk.newarch.local.models.other.UserStatus.* @@ -180,7 +181,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration startKoin { androidContext(this@NextcloudTalkApplication) androidLogger() - modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule, ServiceModule, AccountModule, UseCasesModule, ContactsFlowModule)) + modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule, ServiceModule, AccountModule, UseCasesModule, ContactsFlowModule, SettingsModule)) } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.kt b/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.kt index f7f2b1a13..881f4efb7 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.kt @@ -204,7 +204,12 @@ class RingtoneSelectionController(args: Bundle) : BaseController(), FlexibleAdap } override fun getTitle(): String? { - return resources!!.getString(R.string.nc_settings_notification_sounds) + return if (callNotificationSounds) { + resources?.getString(R.string.nc_settings_calls_sound) + } else { + resources?.getString(R.string.nc_settings_notifications_sound) + + } } @SuppressLint("LongLogTag") diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt deleted file mode 100644 index 6570c36f8..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ /dev/null @@ -1,852 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.controllers - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.app.Activity -import android.app.KeyguardManager -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.security.KeyChain -import android.text.TextUtils -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import android.widget.Checkable -import android.widget.ImageView -import android.widget.TextView -import androidx.core.view.ViewCompat -import androidx.emoji.widget.EmojiTextView -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import butterknife.BindView -import butterknife.OnClick -import coil.api.load -import coil.transform.CircleCropTransformation -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler -import com.bluelinelabs.logansquare.LoganSquare -import com.nextcloud.talk.BuildConfig -import com.nextcloud.talk.R -import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.controllers.base.BaseController -import com.nextcloud.talk.jobs.AccountRemovalWorker -import com.nextcloud.talk.models.RingtoneSettings -import com.nextcloud.talk.models.json.userprofile.UserProfileOverall -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryView -import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.newarch.local.models.getCredentials -import com.nextcloud.talk.newarch.local.models.other.UserStatus -import com.nextcloud.talk.utils.* -import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.preferences.MagicUserInputModule -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder -import com.uber.autodispose.AutoDispose -import com.uber.autodispose.ObservableSubscribeProxy -import com.yarolegovich.lovelydialog.LovelySaveStateHandler -import com.yarolegovich.lovelydialog.LovelyStandardDialog -import com.yarolegovich.mp.* -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.* -import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener -import org.koin.android.ext.android.inject -import java.io.IOException -import java.net.URI -import java.net.URISyntaxException -import java.util.* - -class SettingsController : BaseController() { - @JvmField - @BindView(R.id.settings_screen) - var settingsScreen: MaterialPreferenceScreen? = null - - @JvmField - @BindView(R.id.settings_proxy_choice) - var proxyChoice: MaterialChoicePreference? = null - - @JvmField - @BindView(R.id.settings_proxy_port_edit) - var proxyPortEditText: MaterialEditTextPreference? = null - - @JvmField - @BindView(R.id.settings_licence) - var licenceButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_privacy) - var privacyButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_source_code) - var sourceCodeButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_version) - var versionInfo: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.avatar_image) - var avatarImageView: ImageView? = null - - @JvmField - @BindView(R.id.display_name_text) - var displayNameTextView: EmojiTextView? = null - - @JvmField - @BindView(R.id.base_url_text) - var baseUrlTextView: TextView? = null - - @JvmField - @BindView(R.id.settings_call_sound) - var settingsCallSound: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_message_sound) - var settingsMessageSound: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_remove_account) - var removeAccountButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_switch) - var switchAccountButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_reauthorize) - var reauthorizeButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_add_account) - var addAccountButton: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.message_view) - var messageView: MaterialPreferenceCategory? = null - - @JvmField - @BindView(R.id.settings_client_cert) - var certificateSetup: MaterialStandardPreference? = null - - @JvmField - @BindView(R.id.settings_always_vibrate) - var shouldVibrateSwitchPreference: MaterialSwitchPreference? = null - - @JvmField - @BindView(R.id.settings_incognito_keyboard) - var incognitoKeyboardSwitchPreference: MaterialSwitchPreference? = null - - @JvmField - @BindView(R.id.settings_screen_security) - var screenSecuritySwitchPreference: MaterialSwitchPreference? = null - - @JvmField - @BindView(R.id.settings_link_previews) - var linkPreviewsSwitchPreference: MaterialSwitchPreference? = null - - @JvmField - @BindView(R.id.settings_screen_lock) - var screenLockSwitchPreference: MaterialSwitchPreference? = null - - @JvmField - @BindView(R.id.settings_screen_lock_timeout) - var screenLockTimeoutChoicePreference: MaterialChoicePreference? = null - - @JvmField - @BindView(R.id.message_text) - var messageText: TextView? = null - val ncApi: NcApi by inject() - val usersRepository: UsersRepository by inject() - private var saveStateHandler: LovelySaveStateHandler? = null - private var currentUser: UserNgEntity? = null - private var credentials: String? = null - lateinit var proxyTypeChangeListener: OnPreferenceValueChangedListener - lateinit var proxyCredentialsChangeListener: OnPreferenceValueChangedListener - lateinit var screenSecurityChangeListener: OnPreferenceValueChangedListener - lateinit var screenLockChangeListener: OnPreferenceValueChangedListener - lateinit var screenLockTimeoutChangeListener: OnPreferenceValueChangedListener - lateinit var themeChangeListener: OnPreferenceValueChangedListener - - override fun inflateView( - inflater: LayoutInflater, - container: ViewGroup - ): View { - return inflater.inflate(R.layout.controller_settings, container, false) - } - - override fun onViewBound(view: View) { - super.onViewBound(view) - setHasOptionsMenu(true) - - ViewCompat.setTransitionName(avatarImageView!!, "userAvatar.transitionTag") - - if (saveStateHandler == null) { - saveStateHandler = LovelySaveStateHandler() - } - - proxyTypeChangeListener = ProxyTypeChangeListener() - proxyCredentialsChangeListener = ProxyCredentialsChangeListener() - screenSecurityChangeListener = ScreenSecurityChangeListener() - screenLockChangeListener = ScreenLockListener() - screenLockTimeoutChangeListener = ScreenLockTimeoutListener() - themeChangeListener = ThemeChangeListener() - } - - private fun showLovelyDialog( - dialogId: Int, - savedInstanceState: Bundle? - ) { - when (dialogId) { - ID_REMOVE_ACCOUNT_WARNING_DIALOG -> showRemoveAccountWarning(savedInstanceState) - else -> { - } - } - } - - @OnClick(R.id.settings_version) - fun sendLogs() { - if (resources!!.getBoolean(R.bool.nc_is_debug)) { - LoggingUtils.sendMailWithAttachment(context) - } - } - - override fun onSaveViewState( - view: View, - outState: Bundle - ) { - saveStateHandler!!.saveInstanceState(outState) - super.onSaveViewState(view, outState) - } - - override fun onRestoreViewState( - view: View, - savedViewState: Bundle - ) { - super.onRestoreViewState(view, savedViewState) - if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) { - //Dialog won't be restarted automatically, so we need to call this method. - //Each dialog knows how to restore its viewState - showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState) - } - } - - private fun showRemoveAccountWarning(savedInstanceState: Bundle?) { - if (activity != null) { - LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL) - .setTopColorRes(R.color.nc_darkRed) - .setIcon( - DisplayUtils.getTintedDrawable( - resources!!, - R.drawable.ic_delete_black_24dp, R.color.bg_default - ) - ) - .setPositiveButtonColor(context.resources.getColor(R.color.nc_darkRed)) - .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, null) - .setInstanceStateHandler(ID_REMOVE_ACCOUNT_WARNING_DIALOG, saveStateHandler!!) - .setSavedInstanceState(savedInstanceState) - .show() - } - } - - private fun removeCurrentAccount() { - GlobalScope.launch { - val job = async { - val user = usersRepository.getActiveUser() - user!!.status = UserStatus.PENDING_DELETE - usersRepository.updateUser(user) - val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java) - .build() - WorkManager.getInstance() - .enqueue(accountRemovalWork) - } - job.await() - - if (usersRepository.setAnyUserAsActive()) { - withContext(Dispatchers.Main) { - onViewBound(view!!) - onAttach(view!!) - } - } else { - withContext(Dispatchers.Main) { - router.setRoot(RouterTransaction.with( - ServerEntryView() - ) - .pushChangeHandler(VerticalChangeHandler()) - .popChangeHandler(VerticalChangeHandler()) - ) - } - } - } - } - - override fun onAttach(view: View) { - super.onAttach(view) - - if (actionBar != null) { - actionBar!!.show() - } - - GlobalScope.launch { - var hasMultipleUsers = false - val job = async { - currentUser = usersRepository.getActiveUser() - hasMultipleUsers = usersRepository.getUsers().isNotEmpty() - credentials = currentUser!!.getCredentials() - } - - job.await() - withContext(Dispatchers.Main) { - appPreferences.registerProxyTypeListener(proxyTypeChangeListener) - appPreferences.registerProxyCredentialsListener { proxyCredentialsChangeListener } - appPreferences.registerScreenSecurityListener { screenSecurityChangeListener } - appPreferences.registerScreenLockListener { screenLockChangeListener } - appPreferences.registerScreenLockTimeoutListener { screenLockTimeoutChangeListener } - appPreferences.registerThemeChangeListener { themeChangeListener } - - val listWithIntFields = ArrayList() - listWithIntFields.add("proxy_port") - - settingsScreen!!.setUserInputModule(MagicUserInputModule(activity, listWithIntFields)) - settingsScreen!!.setVisibilityController( - R.id.settings_proxy_use_credentials, - Arrays.asList(R.id.settings_proxy_username_edit, R.id.settings_proxy_password_edit), - true - ) - - if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) { - licenceButton!!.addPreferenceClickListener { view1 -> - val browserIntent = - Intent(Intent.ACTION_VIEW, Uri.parse(resources!!.getString(R.string.nc_gpl3_url))) - startActivity(browserIntent) - } - } else { - licenceButton!!.visibility = View.GONE - } - - if (!DoNotDisturbUtils.hasVibrator()) { - shouldVibrateSwitchPreference!!.visibility = View.GONE - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - incognitoKeyboardSwitchPreference!!.visibility = View.GONE - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - screenLockSwitchPreference!!.visibility = View.GONE - screenLockTimeoutChoicePreference!!.visibility = View.GONE - } else { - screenLockSwitchPreference!!.setSummary( - String.format( - Locale.getDefault(), - resources!!.getString(R.string.nc_settings_screen_lock_desc), - resources!!.getString(R.string.nc_app_name) - ) - ) - } - - if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_privacy_url))) { - privacyButton!!.addPreferenceClickListener { view12 -> - val browserIntent = - Intent(Intent.ACTION_VIEW, Uri.parse(resources!!.getString(R.string.nc_privacy_url))) - startActivity(browserIntent) - } - } else { - privacyButton!!.visibility = View.GONE - } - - if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_source_code_url))) { - sourceCodeButton!!.addPreferenceClickListener { view13 -> - val browserIntent = - Intent(Intent.ACTION_VIEW, Uri.parse(resources!!.getString(R.string.nc_source_code_url))) - startActivity(browserIntent) - } - } else { - sourceCodeButton!!.visibility = View.GONE - } - - versionInfo!!.setSummary("v" + BuildConfig.VERSION_NAME) - - settingsCallSound!!.setOnClickListener { v -> - val bundle = Bundle() - bundle.putBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, true) - router.pushController( - RouterTransaction.with(RingtoneSelectionController(bundle)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) - } - - settingsMessageSound!!.setOnClickListener { v -> - val bundle = Bundle() - bundle.putBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, false) - router.pushController( - RouterTransaction.with(RingtoneSelectionController(bundle)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler()) - ) - } - - addAccountButton!!.addPreferenceClickListener { view15 -> - router.pushController( - RouterTransaction.with(ServerEntryView()).pushChangeHandler( - VerticalChangeHandler() - ) - .popChangeHandler(VerticalChangeHandler()) - ) - } - - switchAccountButton!!.addPreferenceClickListener { view16 -> - router.pushController( - RouterTransaction.with(SwitchAccountController()).pushChangeHandler( - VerticalChangeHandler() - ) - .popChangeHandler(VerticalChangeHandler()) - ) - } - - var host: String? = null - var port = -1 - - val uri: URI - try { - uri = URI(currentUser!!.baseUrl) - host = uri.host - port = uri.port - } catch (e: URISyntaxException) { - Log.e(TAG, "Failed to create uri") - } - - val finalHost = host - val finalPort = port - certificateSetup!!.addPreferenceClickListener { v -> - KeyChain.choosePrivateKeyAlias( - Objects.requireNonNull(activity), { alias -> - activity!!.runOnUiThread { - if (alias != null) { - certificateSetup!!.setTitle(R.string.nc_client_cert_change) - } else { - certificateSetup!!.setTitle(R.string.nc_client_cert_setup) - } - } - - var realAlias = alias - if (realAlias == null) { - realAlias = "" - } - - currentUser = usersRepository.getUserWithId(currentUser!!.id) - currentUser!!.clientCertificate = realAlias - GlobalScope.launch { - usersRepository.updateUser(currentUser!!) - } - }, arrayOf("RSA", "EC"), null, finalHost, finalPort, - currentUser!!.clientCertificate - ) - } - - if (!TextUtils.isEmpty(currentUser!!.clientCertificate)) { - certificateSetup!!.setTitle(R.string.nc_client_cert_change) - } else { - certificateSetup!!.setTitle(R.string.nc_client_cert_setup) - } - - if (shouldVibrateSwitchPreference!!.visibility == View.VISIBLE) { - (shouldVibrateSwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = appPreferences.shouldVibrateSetting - } - - (screenSecuritySwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = appPreferences.isScreenSecured - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - (incognitoKeyboardSwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = - appPreferences.isKeyboardIncognito - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - (incognitoKeyboardSwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = - appPreferences.isKeyboardIncognito - } - - (linkPreviewsSwitchPreference!!.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences.areLinkPreviewsAllowed - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - - if (keyguardManager.isKeyguardSecure) { - screenLockSwitchPreference!!.isEnabled = true - screenLockTimeoutChoicePreference!!.isEnabled = true - (screenLockSwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = - appPreferences.isScreenLocked - - screenLockTimeoutChoicePreference!!.isEnabled = appPreferences.isScreenLocked - - if (appPreferences.isScreenLocked) { - screenLockTimeoutChoicePreference!!.alpha = 1.0f - } else { - screenLockTimeoutChoicePreference!!.alpha = 0.38f - } - - screenLockSwitchPreference!!.alpha = 1.0f - } else { - screenLockSwitchPreference!!.isEnabled = false - screenLockTimeoutChoicePreference!!.isEnabled = false - appPreferences.removeScreenLock() - appPreferences.removeScreenLockTimeout() - (screenLockSwitchPreference!!.findViewById( - R.id.mp_checkable - ) as Checkable).isChecked = - false - screenLockSwitchPreference!!.alpha = 0.38f - screenLockTimeoutChoicePreference!!.alpha = 0.38f - } - } - - var ringtoneName = "" - var ringtoneSettings: RingtoneSettings - if (!TextUtils.isEmpty(appPreferences.callRingtoneUri)) { - try { - ringtoneSettings = - LoganSquare.parse(appPreferences.callRingtoneUri, RingtoneSettings::class.java) - ringtoneName = ringtoneSettings.ringtoneName - } catch (e: IOException) { - Log.e(TAG, "Failed to parse ringtone name") - } - - settingsCallSound!!.setSummary(ringtoneName) - } else { - settingsCallSound!!.setSummary(R.string.nc_settings_default_ringtone) - } - - ringtoneName = "" - - if (!TextUtils.isEmpty(appPreferences.messageRingtoneUri)) { - try { - ringtoneSettings = - LoganSquare.parse(appPreferences.messageRingtoneUri, RingtoneSettings::class.java) - ringtoneName = ringtoneSettings.ringtoneName - } catch (e: IOException) { - Log.e(TAG, "Failed to parse ringtone name") - } - - settingsMessageSound!!.setSummary(ringtoneName) - } else { - settingsMessageSound!!.setSummary(R.string.nc_settings_default_ringtone) - } - - if ("No proxy" == appPreferences.proxyType || appPreferences.proxyType == null) { - hideProxySettings() - } else { - showProxySettings() - } - - if (appPreferences.proxyCredentials) { - showProxyCredentials() - } else { - hideProxyCredentials() - } - - if (currentUser != null) { - - baseUrlTextView!!.text = Uri.parse(currentUser!!.baseUrl) - .host - - reauthorizeButton!!.addPreferenceClickListener { view14 -> - } - - if (currentUser!!.displayName != null) { - displayNameTextView!!.text = currentUser!!.displayName - } - - loadAvatarImage() - - ncApi.getUserProfile( - credentials, - ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl) - ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .`as`>( - AutoDispose.autoDisposable(scopeProvider) - ) - .subscribe({ userProfileOverall -> - - var displayName: String? = userProfileOverall.ocs.data.displayName - - if (!TextUtils.isEmpty(displayName) && displayName != currentUser!!.displayName) { - val user = usersRepository.getUserWithId(currentUser!!.id) - user.displayName = displayName - GlobalScope.launch { - usersRepository.updateUser(user) - } - displayNameTextView!!.text = displayName - - } - }, { throwable -> }, { Log.d(TAG, "") }) - - removeAccountButton!!.addPreferenceClickListener { view1 -> - showLovelyDialog( - ID_REMOVE_ACCOUNT_WARNING_DIALOG, null - ) - } - } - - if (!hasMultipleUsers) { - switchAccountButton!!.visibility = View.GONE - } - - if (ApplicationWideMessageHolder.getInstance().messageType != null) { - when (ApplicationWideMessageHolder.getInstance().messageType) { - ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED -> { - messageText!!.setTextColor(resources!!.getColor(R.color.colorPrimary)) - messageText!!.text = resources!!.getString(R.string.nc_settings_account_updated) - messageView!!.visibility = View.VISIBLE - } - ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> { - messageText!!.setTextColor(resources!!.getColor(R.color.nc_darkRed)) - messageText!!.text = resources!!.getString(R.string.nc_settings_wrong_account) - messageView!!.visibility = View.VISIBLE - messageText!!.setTextColor(resources!!.getColor(R.color.colorPrimary)) - messageText!!.text = resources!!.getString(R.string.nc_server_account_imported) - messageView!!.visibility = View.VISIBLE - } - ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED -> { - messageText!!.setTextColor(resources!!.getColor(R.color.colorPrimary)) - messageText!!.text = resources!!.getString(R.string.nc_server_account_imported) - messageView!!.visibility = View.VISIBLE - } - ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> { - messageText!!.setTextColor(resources!!.getColor(R.color.nc_darkRed)) - messageText!!.text = resources!!.getString(R.string.nc_server_failed_to_import_account) - messageView!!.visibility = View.VISIBLE - } - else -> messageView!!.visibility = View.GONE - } - ApplicationWideMessageHolder.getInstance().messageType = null - - messageView!!.animate() - .translationY(0f) - .alpha(0.0f) - .setDuration(2500) - .setStartDelay(5000) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - if (messageView != null) { - messageView!!.visibility = View.GONE - } - } - }) - } else { - if (messageView != null) { - messageView!!.visibility = View.GONE - } else { - // do nothing - } - } - } - } - - - } - - private fun loadAvatarImage() { - val avatarId: String - if (!TextUtils.isEmpty(currentUser!!.userId)) { - avatarId = currentUser!!.userId - } else { - avatarId = currentUser!!.username - } - - avatarImageView!!.load( - ApiUtils.getUrlForAvatarWithName( - currentUser!!.baseUrl, - avatarId, R.dimen.avatar_size_big - ) - ) { - addHeader("Authorization", currentUser!!.getCredentials()) - transformations(CircleCropTransformation()) - } - } - - public override fun onDestroy() { - appPreferences.unregisterProxyTypeListener(proxyTypeChangeListener) - appPreferences.unregisterProxyCredentialsListener(proxyCredentialsChangeListener) - appPreferences.unregisterScreenSecurityListener(screenSecurityChangeListener) - appPreferences.unregisterScreenLockListener(screenLockChangeListener) - appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener) - appPreferences.unregisterThemeChangeListener(themeChangeListener) - super.onDestroy() - } - - private fun hideProxySettings() { - appPreferences.removeProxyHost() - appPreferences.removeProxyPort() - appPreferences.removeProxyCredentials() - appPreferences.removeProxyUsername() - appPreferences.removeProxyPassword() - settingsScreen!!.findViewById(R.id.settings_proxy_host_edit) - .visibility = View.GONE - settingsScreen!!.findViewById(R.id.settings_proxy_port_edit) - .visibility = View.GONE - settingsScreen!!.findViewById(R.id.settings_proxy_use_credentials) - .visibility = View.GONE - settingsScreen!!.findViewById(R.id.settings_proxy_username_edit) - .visibility = View.GONE - settingsScreen!!.findViewById(R.id.settings_proxy_password_edit) - .visibility = View.GONE - } - - private fun showProxySettings() { - settingsScreen!!.findViewById(R.id.settings_proxy_host_edit) - .visibility = View.VISIBLE - settingsScreen!!.findViewById(R.id.settings_proxy_port_edit) - .visibility = View.VISIBLE - settingsScreen!!.findViewById(R.id.settings_proxy_use_credentials) - .visibility = View.VISIBLE - } - - private fun showProxyCredentials() { - settingsScreen!!.findViewById(R.id.settings_proxy_username_edit) - .visibility = View.VISIBLE - settingsScreen!!.findViewById(R.id.settings_proxy_password_edit) - .visibility = View.VISIBLE - } - - private fun hideProxyCredentials() { - appPreferences.removeProxyUsername() - appPreferences.removeProxyPassword() - settingsScreen!!.findViewById(R.id.settings_proxy_username_edit) - .visibility = View.GONE - settingsScreen!!.findViewById(R.id.settings_proxy_password_edit) - .visibility = View.GONE - } - - override fun getTitle(): String? { - return resources!!.getString(R.string.nc_settings) - } - - private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener { - - override fun onChanged(newValue: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - SecurityUtils.createKey(appPreferences.screenLockTimeout) - } - } - } - - private inner class ScreenLockListener : OnPreferenceValueChangedListener { - - override fun onChanged(newValue: Boolean?) { - screenLockTimeoutChoicePreference!!.isEnabled = newValue!! - - if (newValue) { - screenLockTimeoutChoicePreference!!.alpha = 1.0f - } else { - screenLockTimeoutChoicePreference!!.alpha = 0.38f - } - } - } - - private inner class ScreenSecurityChangeListener : OnPreferenceValueChangedListener { - - override fun onChanged(newValue: Boolean) { - if (newValue) { - if (activity != null) { - activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - } else { - if (activity != null) { - activity!!.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - } - } - } - - private inner class ProxyCredentialsChangeListener : OnPreferenceValueChangedListener { - - override fun onChanged(newValue: Boolean) { - if (newValue) { - showProxyCredentials() - } else { - hideProxyCredentials() - } - } - } - - private inner class ProxyTypeChangeListener : OnPreferenceValueChangedListener { - - override fun onChanged(newValue: String) { - if ("No proxy" == newValue) { - hideProxySettings() - } else { - when (newValue) { - "HTTP" -> if (proxyPortEditText != null) { - proxyPortEditText!!.value = "3128" - } - "DIRECT" -> if (proxyPortEditText != null) { - proxyPortEditText!!.value = "8080" - } - "SOCKS" -> if (proxyPortEditText != null) { - proxyPortEditText!!.value = "1080" - } - else -> { - } - } - - showProxySettings() - } - } - } - - private inner class ThemeChangeListener : OnPreferenceValueChangedListener { - override fun onChanged(newValue: String) { - NextcloudTalkApplication.setAppTheme(newValue) - } - } - - companion object { - - val TAG = "SettingsController" - private val ID_REMOVE_ACCOUNT_WARNING_DIALOG = 0 - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt deleted file mode 100644 index 2ebe16992..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at: - * https://github.com/nextcloud/ownCloud-Account-Importer - */ - -package com.nextcloud.talk.controllers - -import android.accounts.Account -import android.os.Bundle -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import butterknife.BindView -import com.nextcloud.talk.R -import com.nextcloud.talk.adapters.items.AdvancedUserItem -import com.nextcloud.talk.controllers.base.BaseController -import com.nextcloud.talk.models.ImportAccount -import com.nextcloud.talk.models.json.participants.Participant -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.newarch.local.models.other.UserStatus -import com.nextcloud.talk.utils.AccountUtils -import com.nextcloud.talk.utils.bundle.BundleKeys -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject -import java.net.CookieManager -import java.util.* - -class SwitchAccountController : BaseController { - @JvmField - @BindView(R.id.recyclerView) - internal var recyclerView: RecyclerView? = null - - val cookieManager: CookieManager by inject() - val usersRepository: UsersRepository by inject() - - @JvmField - @BindView(R.id.swipe_refresh_layout) - internal var swipeRefreshLayout: SwipeRefreshLayout? = null - private var adapter: FlexibleAdapter>? = null - private val userItems = ArrayList>() - - private var isAccountImport = false - - private val onImportItemClickListener = FlexibleAdapter.OnItemClickListener { view, position -> - if (userItems.size > position) { - val account = (userItems[position] as AdvancedUserItem).account - reauthorizeFromImport(account) - } - - true - } - - private val onSwitchItemClickListener = FlexibleAdapter.OnItemClickListener { view, position -> - if (userItems.size > position) { - val userEntity = (userItems[position] as AdvancedUserItem).entity - GlobalScope.launch { - usersRepository.setUserAsActiveWithId(userEntity!!.id) - cookieManager.cookieStore.removeAll() - withContext(Dispatchers.Main) { - router.popCurrentController() - } - } - } - - true - } - - constructor() { - setHasOptionsMenu(true) - } - - constructor(args: Bundle) : super() { - setHasOptionsMenu(true) - - if (args.containsKey(BundleKeys.KEY_IS_ACCOUNT_IMPORT)) { - isAccountImport = true - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - router.popCurrentController() - return true - } - else -> return super.onOptionsItemSelected(item) - } - } - - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(R.layout.controller_generic_rv, container, false) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup - ): View { - swipeRefreshLayout?.isEnabled = false - actionBar?.show() - - adapter = FlexibleAdapter(userItems, activity, false) - GlobalScope.launch { - val users = usersRepository.getUsers() - var userEntity: UserNgEntity - var participant: Participant - - if (isAccountImport) { - var account: Account - var importAccount: ImportAccount - for (accountObject in AccountUtils.findAccounts(users)) { - account = accountObject - importAccount = AccountUtils.getInformationFromAccount(account) - - participant = Participant() - participant.name = importAccount.username - participant.userId = importAccount.username - userEntity = UserNgEntity(-1, "!", "!", importAccount.baseUrl) - userItems.add(AdvancedUserItem(participant, userEntity, account)) - } - - adapter!!.addListener(onImportItemClickListener) - withContext(Dispatchers.Main) { - adapter!!.updateDataSet(userItems, false) - } - - - } else { - for (userEntityObject in users) { - userEntity = userEntityObject - if (userEntity.status != UserStatus.ACTIVE) { - participant = Participant() - participant.name = userEntity.displayName - - val userId: String - - if (userEntity.userId != null) { - userId = userEntity.userId - } else { - userId = userEntity.username - } - participant.userId = userId - userItems.add(AdvancedUserItem(participant, userEntity, null)) - } - } - - adapter!!.addListener(onSwitchItemClickListener) - withContext(Dispatchers.Main) { - adapter!!.updateDataSet(userItems, false) - } - - } - - } - return super.onCreateView(inflater, container) - } - - override fun onViewBound(view: View) { - super.onViewBound(view) - swipeRefreshLayout!!.isEnabled = false - - if (actionBar != null) { - actionBar!!.show() - } - - /* - - if (adapter == null) { - adapter = FlexibleAdapter(userItems, activity, false) - - var userEntity: UserNgEntity - var participant: Participant - - if (!isAccountImport) { - for (userEntityObject in userUtils!!.users) { - userEntity = userEntityObject as UserEntity - if (!userEntity.getCurrent()) { - participant = Participant() - participant.setName(userEntity.displayName) - - val userId: String - - if (userEntity.userId != null) { - userId = userEntity.userId - } else { - userId = userEntity.username - } - participant.setUserId(userId) - userItems.add(AdvancedUserItem(participant, userEntity, null)) - } - } - - adapter!!.addListener(onSwitchItemClickListener) - adapter!!.updateDataSet(userItems, false) - } else { - var account: Account - var importAccount: ImportAccount - for (accountObject in AccountUtils.findAccounts(userUtils!!.users)) { - account = accountObject - importAccount = AccountUtils.getInformationFromAccount(account) - - participant = Participant() - participant.name = importAccount.username - participant.userId = importAccount.username - userEntity = UserEntity() - userEntity.baseUrl = importAccount.getBaseUrl() - userItems.add(AdvancedUserItem(participant, userEntity, account)) - } - - adapter!!.addListener(onImportItemClickListener) - adapter!!.updateDataSet(userItems, false) - } - }*/ - - prepareViews() - } - - private fun prepareViews() { - val layoutManager = SmoothScrollLinearLayoutManager(activity!!) - recyclerView!!.layoutManager = layoutManager - recyclerView!!.setHasFixedSize(true) - recyclerView!!.adapter = adapter - - swipeRefreshLayout!!.isEnabled = false - } - - private fun reauthorizeFromImport(account: Account?) { - val importAccount = AccountUtils.getInformationFromAccount(account!!) - val bundle = Bundle() - bundle.putString(BundleKeys.KEY_BASE_URL, importAccount.baseUrl) - bundle.putString(BundleKeys.KEY_USERNAME, importAccount.username) - bundle.putString(BundleKeys.KEY_TOKEN, importAccount.token) - bundle.putBoolean(BundleKeys.KEY_IS_ACCOUNT_IMPORT, true) - } - - override fun getTitle(): String? { - return resources!!.getString(R.string.nc_select_an_account) - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt index 0428f89a9..26f756f1a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt @@ -32,7 +32,6 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.ProgressBar -import android.widget.Toolbar import androidx.annotation.RequiresApi import androidx.appcompat.app.ActionBar import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -45,10 +44,8 @@ import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.floatingactionbutton.FloatingActionButton import com.nextcloud.talk.R import com.nextcloud.talk.activities.MainActivity -import com.nextcloud.talk.controllers.SwitchAccountController import com.nextcloud.talk.controllers.base.providers.ActionBarProvider import com.nextcloud.talk.newarch.utils.dp -import com.nextcloud.talk.newarch.utils.px import com.nextcloud.talk.utils.preferences.AppPreferences import com.uber.autodispose.lifecycle.LifecycleScopeProvider import kotlinx.android.synthetic.main.activity_main.* @@ -56,7 +53,6 @@ import kotlinx.android.synthetic.main.search_layout.* import kotlinx.android.synthetic.main.search_layout.view.* import org.greenrobot.eventbus.EventBus import org.koin.android.ext.android.inject -import java.util.* abstract class BaseController : ButterKnifeController(), ComponentCallbacks { enum class AppBarLayoutType { @@ -213,20 +209,9 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks { } - private fun cleanTempCertPreference() { - val temporaryClassNames = ArrayList() - temporaryClassNames.add(SwitchAccountController::class.java.name) - - if (!temporaryClassNames.contains(javaClass.name)) { - appPreferences.removeTemporaryClientCertAlias() - } - - } - override fun onViewBound(view: View) { super.onViewBound(view) - cleanTempCertPreference() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) { disableKeyboardPersonalisedLearning(view as ViewGroup) diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt index 895d49d77..f9b004159 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt @@ -24,9 +24,12 @@ package com.nextcloud.talk.newarch.data.repository.offline import androidx.lifecycle.LiveData import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.local.dao.UsersDao +import com.nextcloud.talk.newarch.local.models.User import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.local.models.toUser class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { override fun getActiveUserLiveData(): LiveData { @@ -45,6 +48,14 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { return usersDao.getUserWithId(id) } + override fun getUsersLiveData(): LiveData> { + return usersDao.getUsersLiveData().distinctUntilChanged().map { usersList -> + usersList.map { + it.toUser() + } + } + } + override suspend fun getUserWithUsernameAndServer( username: String, server: String @@ -72,4 +83,8 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { return usersDao.setAnyUserAsActive() } + override suspend fun markUserForDeletion(id: Long): Boolean { + return usersDao.markUserForDeletion(id) + } + } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt index 613139fd6..717d46034 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt @@ -23,6 +23,7 @@ package com.nextcloud.talk.newarch.domain.repository.offline import androidx.lifecycle.LiveData +import com.nextcloud.talk.newarch.local.models.User import com.nextcloud.talk.newarch.local.models.UserNgEntity interface UsersRepository { @@ -30,10 +31,12 @@ interface UsersRepository { fun getActiveUser(): UserNgEntity? fun getUsers(): List fun getUserWithId(id: Long): UserNgEntity + fun getUsersLiveData(): LiveData> suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? suspend fun updateUser(user: UserNgEntity): Int suspend fun insertUser(user: UserNgEntity): Long suspend fun setUserAsActiveWithId(id: Long) suspend fun deleteUserWithId(id: Long) suspend fun setAnyUserAsActive(): Boolean + suspend fun markUserForDeletion(id: Long): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt index bced434a5..fe88b3992 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt @@ -267,7 +267,7 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> { - router.popCurrentController() + router.popController(this) return true } R.id.conversation_video_call -> { diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt index 60c41ac89..78c9c1038 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt @@ -41,13 +41,13 @@ import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.nextcloud.talk.R import com.nextcloud.talk.R.drawable -import com.nextcloud.talk.controllers.SettingsController import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.newarch.data.presenters.AdvancedEmptyPresenter import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsView import com.nextcloud.talk.newarch.features.search.DebouncingTextWatcher +import com.nextcloud.talk.newarch.features.settingsflow.settings.SettingsView import com.nextcloud.talk.newarch.local.models.toUser import com.nextcloud.talk.newarch.mvvm.BaseView import com.nextcloud.talk.utils.ConductorRemapping @@ -115,17 +115,13 @@ class ConversationsListView : BaseView() { activity?.settingsButton?.setOnClickListener { val settingsTransitionName = "userAvatar.transitionTag" router.pushController( - RouterTransaction.with(SettingsController()) + RouterTransaction.with(SettingsView()) .pushChangeHandler( TransitionChangeHandlerCompat( SharedElementTransition(arrayListOf(settingsTransitionName)), VerticalChangeHandler() ) ) - .popChangeHandler( - TransitionChangeHandlerCompat( - SharedElementTransition(arrayListOf(settingsTransitionName)), VerticalChangeHandler() - ) - ) + .popChangeHandler(HorizontalChangeHandler()) ) } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/di/module/SettingsModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/di/module/SettingsModule.kt new file mode 100644 index 000000000..6b150c49d --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/di/module/SettingsModule.kt @@ -0,0 +1,22 @@ +package com.nextcloud.talk.newarch.features.settingsflow.di.module + +import android.app.Application +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.features.settingsflow.settings.SettingsViewModelFactory +import com.nextcloud.talk.newarch.services.GlobalService +import com.nextcloud.talk.newarch.utils.NetworkComponents +import org.koin.android.ext.koin.androidApplication +import org.koin.dsl.module + +val SettingsModule = module { + factory { + createSettingsViewModelFactory( + androidApplication(), get(), get(), get(), get() + ) + } +} + +fun createSettingsViewModelFactory(application: Application, usersRepository: UsersRepository, networkComponents: NetworkComponents, apiErrorHandler: ApiErrorHandler, globalService: GlobalService): SettingsViewModelFactory { + return SettingsViewModelFactory(application, usersRepository, networkComponents, apiErrorHandler, globalService) +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/looknfeel/SettingsLookNFeelView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/looknfeel/SettingsLookNFeelView.kt new file mode 100644 index 000000000..0312a8fb2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/looknfeel/SettingsLookNFeelView.kt @@ -0,0 +1,149 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.looknfeel + +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner +import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.controllers.RingtoneSelectionController +import com.nextcloud.talk.models.RingtoneSettings +import com.nextcloud.talk.newarch.mvvm.BaseView +import com.nextcloud.talk.utils.DoNotDisturbUtils +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.uber.autodispose.lifecycle.LifecycleScopeProvider +import kotlinx.android.synthetic.main.settings_looknfeel_view.view.* +import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener +import java.io.IOException + +class SettingsLookNFeelView(private val bundle: Bundle? = null) : BaseView() { + override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this) + override val lifecycleOwner = ControllerLifecycleOwner(this) + private var themeChangeListener: OnPreferenceValueChangedListener = ThemeChangeListener() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { + setHasOptionsMenu(true) + appPreferences.registerThemeChangeListener(themeChangeListener) + val view = super.onCreateView(inflater, container) + + view.settings_call_sound.setOnClickListener { v -> + showRingtoneSelectionScreen(true) + } + + view.settings_message_sound.setOnClickListener { v -> + showRingtoneSelectionScreen(false) + } + + view.settings_always_vibrate.isVisible = DoNotDisturbUtils.hasVibrator() + + return view + } + + override fun onAttach(view: View) { + super.onAttach(view) + + var ringtoneName = "" + var ringtoneSettings: RingtoneSettings + + if (!TextUtils.isEmpty(appPreferences.callRingtoneUri)) { + try { + ringtoneSettings = + LoganSquare.parse(appPreferences.callRingtoneUri, RingtoneSettings::class.java) + ringtoneName = ringtoneSettings.ringtoneName + } catch (e: IOException) { + } + + view.settings_call_sound.setSummary(ringtoneName) + } else { + view.settings_call_sound.setSummary(R.string.nc_settings_default_ringtone) + } + + ringtoneName = "" + + if (!TextUtils.isEmpty(appPreferences.messageRingtoneUri)) { + try { + ringtoneSettings = + LoganSquare.parse(appPreferences.messageRingtoneUri, RingtoneSettings::class.java) + ringtoneName = ringtoneSettings.ringtoneName + } catch (e: IOException) { + } + + view.settings_message_sound.setSummary(ringtoneName) + } else { + view.settings_message_sound.setSummary(R.string.nc_settings_default_ringtone) + } + } + + override fun onDestroyView(view: View) { + appPreferences.unregisterThemeChangeListener(themeChangeListener) + super.onDestroyView(view) + } + + private fun showRingtoneSelectionScreen(callSounds: Boolean) { + val bundle = Bundle() + bundle.putBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, callSounds) + router.pushController( + RouterTransaction.with(RingtoneSelectionController(bundle)) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler()) + ) + } + + override fun onDestroy() { + appPreferences.unregisterThemeChangeListener(themeChangeListener) + super.onDestroy() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + router.popController(this) + return true + } + + return super.onOptionsItemSelected(item) + } + + override fun getLayoutId(): Int { + return R.layout.settings_looknfeel_view + } + + override fun getTitle(): String? { + return resources?.getString(R.string.nc_look_and_feel) + } + + private inner class ThemeChangeListener : OnPreferenceValueChangedListener { + override fun onChanged(newValue: String) { + NextcloudTalkApplication.setAppTheme(newValue) + activity?.recreate() + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/privacy/SettingsPrivacyView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/privacy/SettingsPrivacyView.kt new file mode 100644 index 000000000..dac9e3987 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/privacy/SettingsPrivacyView.kt @@ -0,0 +1,178 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.privacy + +import android.app.KeyguardManager +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner +import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider +import com.nextcloud.talk.R +import com.nextcloud.talk.newarch.mvvm.BaseView +import com.nextcloud.talk.utils.preferences.MagicUserInputModule +import com.uber.autodispose.lifecycle.LifecycleScopeProvider +import kotlinx.android.synthetic.main.settings_privacy_view.view.* +import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener +import java.util.* + +class SettingsPrivacyView(private val bundle: Bundle? = null) : BaseView() { + override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this) + override val lifecycleOwner = ControllerLifecycleOwner(this) + + private var proxyTypeChangeListener: OnPreferenceValueChangedListener = ProxyTypeChangeListener() + private var proxyCredentialsChangeListener: OnPreferenceValueChangedListener = ProxyCredentialsChangeListener() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { + setHasOptionsMenu(true) + val view = super.onCreateView(inflater, container) + + view.settings_incognito_keyboard.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + view.settings_screen_lock.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + view.settings_screen_lock_timeout.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + view.settings_screen_lock.setSummary( + String.format( + Locale.getDefault(), + resources!!.getString(R.string.nc_settings_screen_lock_desc), + resources!!.getString(R.string.nc_app_name) + ) + ) + + val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val keyguardIsSecure = keyguardManager.isKeyguardSecure + view.settings_screen_lock.isEnabled = keyguardIsSecure + view.settings_screen_lock_timeout.isEnabled = keyguardIsSecure + + if (keyguardIsSecure) { + if (appPreferences.isScreenLocked) { + view.settings_screen_lock_timeout.alpha = 1.0f + } else { + view.settings_screen_lock_timeout.alpha = 0.38f + } + + view.settings_screen_lock.alpha = 1.0f + } else { + view.settings_screen_lock.alpha = 0.38f + view.settings_screen_lock_timeout.alpha = 0.38f + } + } + + val listWithIntFields = ArrayList() + listWithIntFields.add("proxy_port") + view.privacy_screen.setUserInputModule(MagicUserInputModule(activity, listWithIntFields)) + + appPreferences.registerProxyTypeListener(proxyTypeChangeListener) + appPreferences.registerProxyCredentialsListener(proxyCredentialsChangeListener) + + setupProxySection(view) + return view + } + + override fun onDestroy() { + appPreferences.unregisterProxyCredentialsListener(proxyCredentialsChangeListener) + appPreferences.unregisterProxyTypeListener(proxyTypeChangeListener) + super.onDestroy() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + router.popController(this) + return true + } + + return super.onOptionsItemSelected(item) + } + + override fun getLayoutId(): Int { + return R.layout.settings_privacy_view + } + + override fun getTitle(): String? { + return resources?.getString(R.string.nc_privacy) + } + + private fun setupProxySection(view: View?) { + if ("No proxy" == appPreferences.proxyType || appPreferences.proxyType == null) { + toggleProxySettingsVisibility(view, false) + } else { + toggleCredentialsSettingsVisibility(view, appPreferences.proxyCredentials) + } + } + + private fun toggleProxySettingsVisibility(view: View?, shouldBeVisible: Boolean) { + view?.settings_proxy_host_edit?.isVisible = shouldBeVisible + view?.settings_proxy_port_edit?.isVisible = shouldBeVisible + view?.settings_proxy_use_credentials?.isVisible = shouldBeVisible + + if (!shouldBeVisible) { + appPreferences.setProxyNeedsCredentials(false) + } + } + + private fun toggleCredentialsSettingsVisibility(view: View?, shouldBeVisible: Boolean) { + view?.settings_proxy_username_edit?.isVisible = shouldBeVisible + view?.settings_proxy_password_edit?.isVisible = shouldBeVisible + } + + private inner class ProxyCredentialsChangeListener : OnPreferenceValueChangedListener { + + override fun onChanged(newValue: Boolean) { + toggleCredentialsSettingsVisibility(view, newValue) + if (!newValue) { + appPreferences.proxyUsername = "" + appPreferences.proxyPassword = "" + } + } + } + + private inner class ProxyTypeChangeListener : OnPreferenceValueChangedListener { + + override fun onChanged(newValue: String) { + if ("No proxy" == newValue) { + toggleProxySettingsVisibility(view, false) + } else { + when (newValue) { + "HTTP" -> { + view?.settings_proxy_port_edit?.value = "3128" + } + "DIRECT" -> { + view?.settings_proxy_port_edit?.value = "8080" + } + "SOCKS" -> { + view?.settings_proxy_port_edit?.value = "1080" + } + else -> { + } + } + + toggleProxySettingsVisibility(view, true) + } + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsPresenter.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsPresenter.kt new file mode 100644 index 000000000..d2bce2cb9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsPresenter.kt @@ -0,0 +1,88 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import coil.api.load +import com.nextcloud.talk.R +import com.nextcloud.talk.newarch.local.models.User +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.newarch.local.models.other.UserStatus +import com.nextcloud.talk.newarch.utils.Images +import com.nextcloud.talk.utils.ApiUtils +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Presenter +import kotlinx.android.synthetic.main.user_item.view.* +import kotlinx.android.synthetic.main.user_item.view.avatarImageView +import org.koin.core.KoinComponent + +open class SettingsPresenter(context: Context, onElementClick: ((Page, Holder, Element) -> Unit)?, private val onMoreOptionsClick: ((User) -> Unit)?) : Presenter(context, onElementClick), KoinComponent { + override val elementTypes: Collection + get() = listOf(SettingsElementType.USER.ordinal, SettingsElementType.NEW_USER.ordinal) + + override fun onCreate(parent: ViewGroup, elementType: Int): Holder { + return Holder(getLayoutInflater().inflate(R.layout.user_item, parent, false)) + } + + override fun onBind(page: Page, holder: Holder, element: Element, payloads: List) { + super.onBind(page, holder, element, payloads) + + if (element.type == SettingsElementType.USER.ordinal) { + val user = element.data as User + holder.itemView.userProgressBar.isVisible = user.status == UserStatus.PENDING_DELETE + + if (user.status == UserStatus.PENDING_DELETE) { + holder.itemView.setBackgroundResource(0) + holder.itemView.userMoreOptionsView.visibility = View.INVISIBLE + } else { + if (user.status == UserStatus.ACTIVE) { + holder.itemView.setBackgroundColor(context.resources.getColor(R.color.colorPrimary)) + holder.itemView.background.alpha = 191 + } else { + holder.itemView.setBackgroundColor(0) + } + holder.itemView.userMoreOptionsView.visibility = View.VISIBLE + holder.itemView.userMoreOptionsView.setOnClickListener { + onMoreOptionsClick?.invoke(user) + } + } + val baseUrl = if (user.status == UserStatus.ACTIVE) "" else " (${user.baseUrl.replace("http://", "").replace("https://", "")})" + val displayName = "${user.displayName}$baseUrl" + holder.itemView.userDisplayName.text = displayName + holder.itemView.avatarImageView.load(ApiUtils.getUrlForAvatarWithName(user.baseUrl, user.userId, R.dimen.avatar_size)) { + addHeader("Authorization", user.getCredentials()) + placeholder(BitmapDrawable(Images().getImageWithBackground(context, R.drawable.ic_user))) + fallback(BitmapDrawable(Images().getImageWithBackground(context, R.drawable.ic_user))) + } + } else { + holder.itemView.userDisplayName.text = context.resources.getString(R.string.nc_settings_new_account) + holder.itemView.avatarImageView.load(Images().getImageWithBackground(context = context, drawableId = R.drawable.ic_add_white_24px, foregroundColorTint = R.color.colorPrimary)) + holder.itemView.userProgressBar.isVisible = false + holder.itemView.userMoreOptionsView.visibility = View.GONE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsUtils.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsUtils.kt new file mode 100644 index 000000000..b6d4e718a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsUtils.kt @@ -0,0 +1,6 @@ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +enum class SettingsElementType { + USER, + NEW_USER, +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsView.kt new file mode 100644 index 000000000..a39107125 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsView.kt @@ -0,0 +1,255 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.core.view.isVisible +import androidx.lifecycle.observe +import androidx.recyclerview.widget.LinearLayoutManager +import coil.Coil +import coil.api.load +import coil.transform.CircleCropTransformation +import com.afollestad.materialdialogs.LayoutMode +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.bottomsheets.BottomSheet +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner +import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler +import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler +import com.nextcloud.talk.BuildConfig +import com.nextcloud.talk.R +import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage +import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage +import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryView +import com.nextcloud.talk.newarch.features.settingsflow.looknfeel.SettingsLookNFeelView +import com.nextcloud.talk.newarch.features.settingsflow.privacy.SettingsPrivacyView +import com.nextcloud.talk.newarch.local.models.User +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.newarch.local.models.toUser +import com.nextcloud.talk.newarch.mvvm.BaseView +import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView +import com.nextcloud.talk.newarch.utils.Images +import com.nextcloud.talk.utils.ApiUtils +import com.otaliastudios.elements.Adapter +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Presenter +import com.uber.autodispose.lifecycle.LifecycleScopeProvider +import kotlinx.android.synthetic.main.settings_view.view.* +import org.koin.android.ext.android.inject + +class SettingsView(private val bundle: Bundle? = null) : BaseView() { + override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this) + override val lifecycleOwner = ControllerLifecycleOwner(this) + + private lateinit var viewModel: SettingsViewModel + val factory: SettingsViewModelFactory by inject() + + private lateinit var settingsUsersAdapter: Adapter + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { + viewModel = viewModelProvider(factory).get(SettingsViewModel::class.java) + val view = super.onCreateView(inflater, container) + setHasOptionsMenu(true) + + setupAboutSection(view) + view.settings_privacy_options.setOnClickListener { + router.pushController(RouterTransaction.with(SettingsPrivacyView()) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler()) + ) + } + + view.settings_look_n_feel.setOnClickListener { + router.pushController(RouterTransaction.with(SettingsLookNFeelView()) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler()) + ) + } + + showFallbackAvatar(view.avatar_image) + + viewModel.activeUser.observe(this@SettingsView) { user -> + view.display_name_text.text = user?.displayName ?: "" + view.base_url_text.text = user?.baseUrl?.replace("http://", "")?.replace("https://", "") + ?: "" + loadAvatar(user?.toUser(), view.avatar_image) + } + + settingsUsersAdapter = Adapter.builder(this) + .addSource(SettingsViewSource(viewModel.users)) + .addSource(SettingsViewFooterSource(activity as Context)) + .addPresenter(Presenter.forLoadingIndicator(activity as Context, R.layout.loading_state)) + .addPresenter(SettingsPresenter(activity as Context, ::onElementClick, ::onMoreOptionsClick)) + .into(view.settingsRecyclerView) + + view.settingsRecyclerView.initRecyclerView(LinearLayoutManager(activity), settingsUsersAdapter, false) + + return view + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + router.popController(this) + return true + } + + return super.onOptionsItemSelected(item) + } + + private fun showFallbackAvatar(target: ImageView) { + val fallbackImage = BitmapDrawable(Images().getImageWithBackground(activity as Context, R.drawable.ic_user)) + Coil.load(context, fallbackImage) { + target(target) + transformations(CircleCropTransformation()) + } + } + + private fun loadAvatar(user: User?, target: ImageView) { + user?.let { + val imageLoader = viewModel.networkComponents.getImageLoader(it) + imageLoader.load(activity as Context, ApiUtils.getUrlForAvatarWithName(it.baseUrl, it.userId, R.dimen.avatar_size_big)) { + target(target) + addHeader("Authorization", user.getCredentials()) + transformations(CircleCropTransformation()) + fallback(BitmapDrawable(Images().getImageWithBackground(activity as Context, R.drawable.ic_user))) + error(BitmapDrawable(Images().getImageWithBackground(activity as Context, R.drawable.ic_user))) + } + } ?: run { + showFallbackAvatar(target) + } + } + + private fun setupAboutSection(view: View) { + val privacyUrl = resources?.getString(R.string.nc_privacy_url) + privacyUrl?.let { privacyUrlString -> + view.settings_privacy.setOnClickListener { + val browserIntent = + Intent(Intent.ACTION_VIEW, Uri.parse(privacyUrlString)) + startActivity(browserIntent) + } + } + + view.settings_privacy.isVisible = !privacyUrl.isNullOrEmpty() + + val sourceCodeUrl = resources?.getString(R.string.nc_source_code_url) + sourceCodeUrl?.let { sourceCodeUrlString -> + view.settings_source_code.setOnClickListener { + val browserIntent = + Intent(Intent.ACTION_VIEW, Uri.parse(sourceCodeUrlString)) + startActivity(browserIntent) + } + } + view.settings_source_code.isVisible = !sourceCodeUrl.isNullOrEmpty() + + val licenceUrl = resources?.getString(R.string.nc_gpl3_url) + licenceUrl?.let { licenceUrlString -> + view.settings_licence.setOnClickListener { + val browserIntent = + Intent(Intent.ACTION_VIEW, Uri.parse(licenceUrlString)) + startActivity(browserIntent) + } + } + view.settings_licence.isVisible = !licenceUrl.isNullOrEmpty() + + view.settings_version.setSummary("v" + BuildConfig.VERSION_NAME) + } + + private fun onMoreOptionsClick(user: User) { + activity?.let { activity -> + MaterialDialog(activity, BottomSheet(LayoutMode.WRAP_CONTENT)).show { + cornerRadius(res = R.dimen.corner_radius) + title(text = user.displayName) + listItemsWithImage(getSettingsMenuItemForUser(user)) { _, _, item -> + when (item.iconRes) { + R.drawable.ic_baseline_clear_24 -> { + val weHaveActiveUser = viewModel.removeUser(user) + if (!weHaveActiveUser) { + router.setRoot(RouterTransaction.with(ServerEntryView()) + .popChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler())) + } + } + } + } + } + } + } + + private fun getSettingsMenuItemForUser(user: User): MutableList { + val items = mutableListOf() + + resources?.let { + /*items.add( + BasicListItemWithImage( + R.drawable.ic_baseline_clear_24, + it.getString(R.string.nc_settings_reauthorize) + ) + ) + items.add( + BasicListItemWithImage( + R.drawable.ic_baseline_clear_24, + it.getString(R.string.nc_client_cert_setup) + ) + )*/ + items.add( + BasicListItemWithImage( + R.drawable.ic_baseline_clear_24, + it.getString(R.string.nc_settings_remove_account) + ) + ) + } + + return items + } + + private fun onElementClick(page: Page, holder: Presenter.Holder, element: Element) { + if (element.type == SettingsElementType.USER.ordinal) { + if (viewModel.setUserAsActive(element.data as User)) { + router.popController(this) + } + } else { + router.pushController(RouterTransaction.with(ServerEntryView()) + .pushChangeHandler(VerticalChangeHandler()) + .popChangeHandler(VerticalChangeHandler()) + ) + } + } + + override fun getLayoutId(): Int { + return R.layout.settings_view + } + + override fun getTitle(): String? { + return resources?.getString(R.string.nc_settings) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewFooterSource.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewFooterSource.kt new file mode 100644 index 000000000..7d3459446 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewFooterSource.kt @@ -0,0 +1,59 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import android.content.Context +import com.nextcloud.talk.R +import com.nextcloud.talk.newarch.local.models.User +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.Source +import com.otaliastudios.elements.extensions.FooterSource + +class SettingsViewFooterSource(private val context: Context) : FooterSource() { + private var lastAnchor: User? = null + + override fun areItemsTheSame(first: Data, second: Data): Boolean { + return first == second + } + + override fun getElementType(data: Data): Int { + return SettingsElementType.NEW_USER.ordinal + } + + override fun dependsOn(source: Source<*>): Boolean { + return source is SettingsViewSource + } + + override fun computeFooters(page: Page, list: List): List> { + val results = arrayListOf>() + lastAnchor = if (list.isNotEmpty()) { + val user = list.takeLast(1)[0] + results.add(Data(user, context.resources.getString(R.string.nc_settings_new_account))) + user + } else { + null + } + + return results + } + +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModel.kt new file mode 100644 index 000000000..7caf75756 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModel.kt @@ -0,0 +1,85 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.models.User +import com.nextcloud.talk.newarch.local.models.other.UserStatus +import com.nextcloud.talk.newarch.mvvm.BaseViewModel +import com.nextcloud.talk.newarch.services.GlobalService +import com.nextcloud.talk.newarch.utils.NetworkComponents +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext + +class SettingsViewModel constructor( + application: Application, + private val usersRepository: UsersRepository, + val networkComponents: NetworkComponents, + private val apiErrorHandler: ApiErrorHandler, + private val globalService: GlobalService +) : BaseViewModel(application) { + val users = usersRepository.getUsersLiveData() + val activeUser = globalService.currentUserLiveData + + fun setUserAsActive(user: User): Boolean { + if (user.status == UserStatus.DORMANT) { + val job = runBlocking { + viewModelScope.launch { + user.id?.let { + usersRepository.setUserAsActiveWithId(it) + } + } + } + + job.start() + return true + } + + return false + } + + private suspend fun removeUserWithId(id: Long): Boolean { + return usersRepository.markUserForDeletion(id) + } + + fun removeUser(user: User): Boolean = runBlocking { + var weHaveActiveUser = true + if (user.status != UserStatus.PENDING_DELETE) { + val userId = user.id + if (userId != null) { + weHaveActiveUser = withContext(Dispatchers.Default) { + runBlocking { + removeUserWithId(userId) + } + } + } + } + + weHaveActiveUser + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModelFactory.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModelFactory.kt new file mode 100644 index 000000000..60bfc549d --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewModelFactory.kt @@ -0,0 +1,45 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.services.GlobalService +import com.nextcloud.talk.newarch.utils.NetworkComponents + +class SettingsViewModelFactory constructor( + private val application: Application, + private val usersRepository: UsersRepository, + private val networkComponents: NetworkComponents, + private val apiErrorHandler: ApiErrorHandler, + private val globalService: GlobalService +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + return SettingsViewModel( + application, usersRepository, networkComponents, apiErrorHandler, globalService + ) as T + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewSource.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewSource.kt new file mode 100644 index 000000000..2c39377b7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewSource.kt @@ -0,0 +1,47 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + +import androidx.lifecycle.LiveData +import com.nextcloud.talk.newarch.local.models.User +import com.otaliastudios.elements.Element +import com.otaliastudios.elements.Page +import com.otaliastudios.elements.extensions.MainSource + +class SettingsViewSource(private val data: LiveData>, loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = false, emptyIndicatorEnabled: Boolean = false) : MainSource(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) { + private var currentPage: Page? = null + override fun onPageOpened(page: Page, dependencies: List>) { + super.onPageOpened(page, dependencies) + if (page.previous() == null) { + currentPage = page + postResult(page, data) + } + } + + override fun areItemsTheSame(first: T, second: T): Boolean { + return first.id == second.id + } + + override fun getElementType(data: T): Int { + return SettingsElementType.USER.ordinal + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewUserPresenter.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewUserPresenter.kt new file mode 100644 index 000000000..94830bad1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/settingsflow/settings/SettingsViewUserPresenter.kt @@ -0,0 +1,23 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see . + * + */ +package com.nextcloud.talk.newarch.features.settingsflow.settings + diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/UserStatusConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/UserStatusConverter.kt index 6082b6f19..4ab75284f 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/UserStatusConverter.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/UserStatusConverter.kt @@ -39,9 +39,9 @@ class UserStatusConverter { @TypeConverter fun fromIntToUserStatus(value: Int): UserStatus? { return when (value) { - 0 -> DORMANT - 1 -> ACTIVE - else -> PENDING_DELETE + 0 -> PENDING_DELETE + 1 -> DORMANT + else -> ACTIVE } } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt index 09e15a712..6d0cc3300 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt @@ -30,12 +30,15 @@ import com.nextcloud.talk.newarch.local.models.other.UserStatus @Dao abstract class UsersDao { // get active user - @Query("SELECT * FROM users where status = 1") + @Query("SELECT * FROM users where status = 2") abstract fun getActiveUser(): UserNgEntity? - @Query("SELECT * FROM users WHERE status = 1") + @Query("SELECT * FROM users WHERE status = 2") abstract fun getActiveUserLiveData(): LiveData + @Query("SELECT * from users ORDER BY status DESC") + abstract fun getUsersLiveData(): LiveData> + @Query("DELETE FROM users WHERE id = :id") abstract suspend fun deleteUserWithId(id: Long) @@ -49,13 +52,13 @@ abstract class UsersDao { abstract suspend fun saveUsers(vararg users: UserNgEntity): List // get all users not scheduled for deletion - @Query("SELECT * FROM users where status != 2") + @Query("SELECT * FROM users where status != 0") abstract fun getUsers(): List @Query("SELECT * FROM users where id = :id") abstract fun getUserWithId(id: Long): UserNgEntity - @Query("SELECT * FROM users where status = 2") + @Query("SELECT * FROM users where status = 0") abstract fun getUsersScheduledForDeletion(): List @Query("SELECT * FROM users WHERE username = :username AND base_url = :server") @@ -75,6 +78,20 @@ abstract class UsersDao { } } + @Transaction + open suspend fun markUserForDeletion(id: Long): Boolean { + val users = getUsers() + for (user in users) { + if (user.id == id) { + user.status = UserStatus.PENDING_DELETE + updateUser(user) + break + } + } + + return setAnyUserAsActive() + } + @Transaction open suspend fun setAnyUserAsActive(): Boolean { val users = getUsers() diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/UserStatus.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/UserStatus.kt index da3bb09ed..10d0e6cbc 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/UserStatus.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/UserStatus.kt @@ -23,12 +23,12 @@ package com.nextcloud.talk.newarch.local.models.other enum class UserStatus { + // account that will be deleted in the near future + PENDING_DELETE, + // account that is NOT actively used by the UI, but might be used by background tasks DORMANT, // currently active account ACTIVE, - - // account that will be deleted in the near future - PENDING_DELETE } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/Images.kt b/app/src/main/java/com/nextcloud/talk/newarch/utils/Images.kt index 7ccfe8fd4..b6e82d47e 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/utils/Images.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/utils/Images.kt @@ -75,7 +75,7 @@ class Images { val layers = arrayOfNulls(2) layers[0] = context.getDrawable(backgroundDrawableId) var scale = 0.25f - if (drawableId == R.drawable.ic_baseline_email_24 || drawableId == R.drawable.ic_link_white_24px) { + if (drawableId == R.drawable.ic_baseline_email_24 || drawableId == R.drawable.ic_link_white_24px || drawableId == R.drawable.ic_add_white_24px) { scale = 0.5f } else if (drawableId == R.drawable.ic_user) { scale = 0.625f diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index 916212a9e..b6c78243a 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -142,6 +142,14 @@ public interface AppPreferences { @RemoveMethod void removePushToTalkIntroShown(); + @KeyByString("call_ringtone") + @RegisterChangeListenerMethod + void registerCallRingtoneListener(OnPreferenceValueChangedListener listener); + + @KeyByString("call_ringtone") + @UnregisterChangeListenerMethod + void unregisterCallRingtoneListener(OnPreferenceValueChangedListener listener); + @KeyByString("call_ringtone") String getCallRingtoneUri(); @@ -152,6 +160,14 @@ public interface AppPreferences { @RemoveMethod void removeCallRingtoneUri(); + @KeyByString("message_ringtone") + @RegisterChangeListenerMethod + void registerMessageRingtoneListener(OnPreferenceValueChangedListener listener); + + @KeyByString("message_ringtone") + @UnregisterChangeListenerMethod + void unregisterMessageRingtoneListener(OnPreferenceValueChangedListener listener); + @KeyByString("message_ringtone") String getMessageRingtoneUri(); diff --git a/app/src/main/res/drawable/ic_baseline_more_vert_24.xml b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml new file mode 100644 index 000000000..9f6859b77 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/controller_settings.xml b/app/src/main/res/layout/controller_settings.xml index 10bf1c4fe..f23e56584 100644 --- a/app/src/main/res/layout/controller_settings.xml +++ b/app/src/main/res/layout/controller_settings.xml @@ -20,6 +20,7 @@ @@ -53,6 +54,7 @@ android:layout_below="@id/avatar_image" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/margin_between_elements" + tools:text="Important user" android:textColor="@color/nc_incoming_text_default" /> @@ -164,120 +168,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/settings_privacy_view.xml b/app/src/main/res/layout/settings_privacy_view.xml new file mode 100644 index 000000000..081923eb6 --- /dev/null +++ b/app/src/main/res/layout/settings_privacy_view.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/settings_view.xml b/app/src/main/res/layout/settings_view.xml new file mode 100644 index 000000000..9bdf59288 --- /dev/null +++ b/app/src/main/res/layout/settings_view.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/user_item.xml b/app/src/main/res/layout/user_item.xml new file mode 100644 index 000000000..2a1b22972 --- /dev/null +++ b/app/src/main/res/layout/user_item.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8b1a1377..00c1f1de4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,6 +75,7 @@ Please confirm your intent to remove the current account. Remove account Add a new account + New account Add Only current account can be reauthorized Talk app is not installed on the server you tried to authenticate against @@ -347,4 +348,8 @@ Reject You were silenced by a moderator Failed to send - tap to retry sending. + Preferences + Look&Feel + Calls sound + Notifications sound