Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2020-04-24 14:19:04 +02:00
parent 29933202d8
commit 07ea11f216
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
32 changed files with 1448 additions and 1268 deletions

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String>
lateinit var proxyCredentialsChangeListener: OnPreferenceValueChangedListener<Boolean>
lateinit var screenSecurityChangeListener: OnPreferenceValueChangedListener<Boolean>
lateinit var screenLockChangeListener: OnPreferenceValueChangedListener<Boolean>
lateinit var screenLockTimeoutChangeListener: OnPreferenceValueChangedListener<String>
lateinit var themeChangeListener: OnPreferenceValueChangedListener<String>
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<String>()
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>(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<View>(
R.id.mp_checkable
) as Checkable).isChecked = appPreferences.shouldVibrateSetting
}
(screenSecuritySwitchPreference!!.findViewById<View>(
R.id.mp_checkable
) as Checkable).isChecked = appPreferences.isScreenSecured
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
(incognitoKeyboardSwitchPreference!!.findViewById<View>(
R.id.mp_checkable
) as Checkable).isChecked =
appPreferences.isKeyboardIncognito
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
(incognitoKeyboardSwitchPreference!!.findViewById<View>(
R.id.mp_checkable
) as Checkable).isChecked =
appPreferences.isKeyboardIncognito
}
(linkPreviewsSwitchPreference!!.findViewById<View>(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<View>(
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<View>(
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`<ObservableSubscribeProxy<UserProfileOverall>>(
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<View>(R.id.settings_proxy_host_edit)
.visibility = View.GONE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_port_edit)
.visibility = View.GONE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_use_credentials)
.visibility = View.GONE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_username_edit)
.visibility = View.GONE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_password_edit)
.visibility = View.GONE
}
private fun showProxySettings() {
settingsScreen!!.findViewById<View>(R.id.settings_proxy_host_edit)
.visibility = View.VISIBLE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_port_edit)
.visibility = View.VISIBLE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_use_credentials)
.visibility = View.VISIBLE
}
private fun showProxyCredentials() {
settingsScreen!!.findViewById<View>(R.id.settings_proxy_username_edit)
.visibility = View.VISIBLE
settingsScreen!!.findViewById<View>(R.id.settings_proxy_password_edit)
.visibility = View.VISIBLE
}
private fun hideProxyCredentials() {
appPreferences.removeProxyUsername()
appPreferences.removeProxyPassword()
settingsScreen!!.findViewById<View>(R.id.settings_proxy_username_edit)
.visibility = View.GONE
settingsScreen!!.findViewById<View>(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<String> {
override fun onChanged(newValue: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SecurityUtils.createKey(appPreferences.screenLockTimeout)
}
}
}
private inner class ScreenLockListener : OnPreferenceValueChangedListener<Boolean> {
override fun onChanged(newValue: Boolean?) {
screenLockTimeoutChoicePreference!!.isEnabled = newValue!!
if (newValue) {
screenLockTimeoutChoicePreference!!.alpha = 1.0f
} else {
screenLockTimeoutChoicePreference!!.alpha = 0.38f
}
}
}
private inner class ScreenSecurityChangeListener : OnPreferenceValueChangedListener<Boolean> {
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<Boolean> {
override fun onChanged(newValue: Boolean) {
if (newValue) {
showProxyCredentials()
} else {
hideProxyCredentials()
}
}
}
private inner class ProxyTypeChangeListener : OnPreferenceValueChangedListener<String> {
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<String> {
override fun onChanged(newValue: String) {
NextcloudTalkApplication.setAppTheme(newValue)
}
}
companion object {
val TAG = "SettingsController"
private val ID_REMOVE_ACCOUNT_WARNING_DIALOG = 0
}
}

View File

@ -1,266 +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 <http://www.gnu.org/licenses/>.
*
* 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<AbstractFlexibleItem<*>>? = null
private val userItems = ArrayList<AbstractFlexibleItem<*>>()
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)
}
}

View File

@ -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<String>()
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)

View File

@ -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<UserNgEntity?> {
@ -45,6 +48,14 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository {
return usersDao.getUserWithId(id)
}
override fun getUsersLiveData(): LiveData<List<User>> {
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)
}
}

View File

@ -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<UserNgEntity>
fun getUserWithId(id: Long): UserNgEntity
fun getUsersLiveData(): LiveData<List<User>>
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
}

View File

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

View File

@ -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())
)
}

View File

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

View File

@ -0,0 +1,149 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<String> = 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<String> {
override fun onChanged(newValue: String) {
NextcloudTalkApplication.setAppTheme(newValue)
activity?.recreate()
}
}
}

View File

@ -0,0 +1,178 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<String> = ProxyTypeChangeListener()
private var proxyCredentialsChangeListener: OnPreferenceValueChangedListener<Boolean> = 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<String>()
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<Boolean> {
override fun onChanged(newValue: Boolean) {
toggleCredentialsSettingsVisibility(view, newValue)
if (!newValue) {
appPreferences.proxyUsername = ""
appPreferences.proxyPassword = ""
}
}
}
private inner class ProxyTypeChangeListener : OnPreferenceValueChangedListener<String> {
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)
}
}
}
}

View File

@ -0,0 +1,88 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<T : Any>(context: Context, onElementClick: ((Page, Holder, Element<T>) -> Unit)?, private val onMoreOptionsClick: ((User) -> Unit)?) : Presenter<T>(context, onElementClick), KoinComponent {
override val elementTypes: Collection<Int>
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<T>, payloads: List<Any>) {
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
}
}
}

View File

@ -0,0 +1,6 @@
package com.nextcloud.talk.newarch.features.settingsflow.settings
enum class SettingsElementType {
USER,
NEW_USER,
}

View File

@ -0,0 +1,255 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<BasicListItemWithImage> {
val items = mutableListOf<BasicListItemWithImage>()
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<Any>) {
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)
}
}

View File

@ -0,0 +1,59 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<User, String>() {
private var lastAnchor: User? = null
override fun areItemsTheSame(first: Data<User, String>, second: Data<User, String>): Boolean {
return first == second
}
override fun getElementType(data: Data<User, String>): Int {
return SettingsElementType.NEW_USER.ordinal
}
override fun dependsOn(source: Source<*>): Boolean {
return source is SettingsViewSource
}
override fun computeFooters(page: Page, list: List<User>): List<Data<User, String>> {
val results = arrayListOf<Data<User, String>>()
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
}
}

View File

@ -0,0 +1,85 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<SettingsView>(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
}
}

View File

@ -0,0 +1,45 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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 <T : ViewModel?> create(modelClass: Class<T>): T {
return SettingsViewModel(
application, usersRepository, networkComponents, apiErrorHandler, globalService
) as T
}
}

View File

@ -0,0 +1,47 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
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<T : User>(private val data: LiveData<List<T>>, loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = false, emptyIndicatorEnabled: Boolean = false) : MainSource<T>(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) {
private var currentPage: Page? = null
override fun onPageOpened(page: Page, dependencies: List<Element<*>>) {
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
}
}

View File

@ -0,0 +1,23 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>.
*
*/
package com.nextcloud.talk.newarch.features.settingsflow.settings

View File

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

View File

@ -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<UserNgEntity?>
@Query("SELECT * from users ORDER BY status DESC")
abstract fun getUsersLiveData(): LiveData<List<UserNgEntity>>
@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<Long>
// 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<UserNgEntity>
@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<UserNgEntity>
@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()

View File

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

View File

@ -75,7 +75,7 @@ class Images {
val layers = arrayOfNulls<Drawable>(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

View File

@ -142,6 +142,14 @@ public interface AppPreferences {
@RemoveMethod
void removePushToTalkIntroShown();
@KeyByString("call_ringtone")
@RegisterChangeListenerMethod
void registerCallRingtoneListener(OnPreferenceValueChangedListener<String> listener);
@KeyByString("call_ringtone")
@UnregisterChangeListenerMethod
void unregisterCallRingtoneListener(OnPreferenceValueChangedListener<String> listener);
@KeyByString("call_ringtone")
String getCallRingtoneUri();
@ -152,6 +160,14 @@ public interface AppPreferences {
@RemoveMethod
void removeCallRingtoneUri();
@KeyByString("message_ringtone")
@RegisterChangeListenerMethod
void registerMessageRingtoneListener(OnPreferenceValueChangedListener<String> listener);
@KeyByString("message_ringtone")
@UnregisterChangeListenerMethod
void unregisterMessageRingtoneListener(OnPreferenceValueChangedListener<String> listener);
@KeyByString("message_ringtone")
String getMessageRingtoneUri();

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -20,6 +20,7 @@
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_screen"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -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" />
<TextView
@ -62,6 +64,7 @@
android:layout_below="@id/display_name_text"
android:layout_centerHorizontal="true"
android:layout_margin="4dp"
tools:text="nextcloud.com"
android:textColor="@color/nc_incoming_text_default" />
<ImageView
@ -69,6 +72,7 @@
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true"
tools:src="@tools:sample/avatars[0]"
android:transitionName="userAvatar.transitionTag" />
@ -164,120 +168,6 @@
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_privacy"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialChoicePreference
android:id="@+id/settings_screen_lock_timeout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@string/nc_screen_lock_timeout_sixty"
apc:mp_entry_descriptions="@array/screen_lock_timeout_descriptions"
apc:mp_entry_values="@array/screen_lock_timeout_entry_values"
apc:mp_key="@string/nc_settings_screen_lock_timeout_key"
apc:mp_show_value="onBottom"
apc:mp_title="@string/nc_settings_screen_lock_timeout_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_lock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_false"
apc:mp_key="@string/nc_settings_screen_lock_key"
apc:mp_title="@string/nc_settings_screen_lock_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_false"
apc:mp_key="@string/nc_settings_screen_security_key"
apc:mp_summary="@string/nc_settings_screen_security_desc"
apc:mp_title="@string/nc_settings_screen_security_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_incognito_keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_true"
apc:mp_key="@string/nc_settings_incognito_keyboard_key"
apc:mp_summary="@string/nc_settings_incognito_keyboard_desc"
apc:mp_title="@string/nc_settings_incognito_keyboard_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_link_previews"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_true"
apc:mp_key="@string/nc_settings_link_previews_key"
apc:mp_summary="@string/nc_settings_link_previews_desc"
apc:mp_title="@string/nc_settings_link_previews_title" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_proxy_title"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialChoicePreference
android:id="@+id/settings_proxy_choice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@string/nc_no_proxy"
apc:mp_entry_descriptions="@array/proxy_type_descriptions"
apc:mp_key="@string/nc_settings_proxy_type_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_type_title"></com.yarolegovich.mp.MaterialChoicePreference>
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_host_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_host_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_host_title" />
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_port_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_port_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_port_title" />
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_username_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_username_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_username" />
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_password_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_password_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_password" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_proxy_use_credentials"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="false"
apc:mp_key="@string/nc_settings_use_credentials_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_use_credentials_title">
</com.yarolegovich.mp.MaterialSwitchPreference>
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
android:id="@+id/lookNFeelScreen"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_appearance"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialChoicePreference
android:id="@+id/settings_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@string/nc_default_theme"
apc:mp_entry_descriptions="@array/theme_descriptions"
apc:mp_entry_values="@array/theme_entry_values"
apc:mp_key="@string/nc_settings_theme_key"
apc:mp_show_value="onBottom"
apc:mp_title="@string/nc_settings_theme_title" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_notification_sounds"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_call_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_call_ringtone_key"
apc:mp_title="@string/nc_settings_call_ringtone" />
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_message_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_message_ringtone_key"
apc:mp_title="@string/nc_settings_other_notifications_ringtone" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_always_vibrate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_true"
apc:mp_key="@string/nc_settings_vibrate_key"
apc:mp_summary="@string/nc_settings_vibrate_desc"
apc:mp_title="@string/nc_settings_vibrate" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
</com.yarolegovich.mp.MaterialPreferenceScreen>

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
android:id="@+id/privacy_screen"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_privacy"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialChoicePreference
android:id="@+id/settings_screen_lock_timeout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@string/nc_screen_lock_timeout_sixty"
apc:mp_entry_descriptions="@array/screen_lock_timeout_descriptions"
apc:mp_entry_values="@array/screen_lock_timeout_entry_values"
apc:mp_key="@string/nc_settings_screen_lock_timeout_key"
apc:mp_show_value="onBottom"
apc:mp_title="@string/nc_settings_screen_lock_timeout_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_lock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_false"
apc:mp_key="@string/nc_settings_screen_lock_key"
apc:mp_title="@string/nc_settings_screen_lock_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_false"
apc:mp_key="@string/nc_settings_screen_security_key"
apc:mp_summary="@string/nc_settings_screen_security_desc"
apc:mp_title="@string/nc_settings_screen_security_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_incognito_keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_true"
apc:mp_key="@string/nc_settings_incognito_keyboard_key"
apc:mp_summary="@string/nc_settings_incognito_keyboard_desc"
apc:mp_title="@string/nc_settings_incognito_keyboard_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_link_previews"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_true"
apc:mp_key="@string/nc_settings_link_previews_key"
apc:mp_summary="@string/nc_settings_link_previews_desc"
apc:mp_title="@string/nc_settings_link_previews_title" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_settings_proxy_title"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialChoicePreference
android:id="@+id/settings_proxy_choice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@string/nc_no_proxy"
apc:mp_entry_descriptions="@array/proxy_type_descriptions"
apc:mp_key="@string/nc_settings_proxy_type_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_type_title"></com.yarolegovich.mp.MaterialChoicePreference>
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_host_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_host_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_host_title" />
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_port_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_port_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_proxy_port_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_proxy_use_credentials"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="false"
apc:mp_key="@string/nc_settings_use_credentials_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_settings_use_credentials_title"/>
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_username_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_username_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_username" />
<com.yarolegovich.mp.MaterialEditTextPreference
android:id="@+id/settings_proxy_password_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_key="@string/nc_settings_proxy_password_key"
apc:mp_show_value="onRight"
apc:mp_title="@string/nc_password" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
</com.yarolegovich.mp.MaterialPreferenceScreen>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_screen"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:focusable="true"
android:layout_centerHorizontal="true"
tools:src="@tools:sample/avatars[0]"
android:transitionName="userAvatar.transitionTag" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/display_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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" />
<TextView
android:id="@+id/base_url_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/display_name_text"
android:layout_centerHorizontal="true"
android:layout_margin="4dp"
tools:text="nextcloud.com"
android:textColor="@color/nc_incoming_text_default"
android:layout_marginBottom="8dp"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/user_item"
tools:itemCount="5"
android:layout_below="@id/base_url_text"
android:id="@+id/settingsRecyclerView"/>
</RelativeLayout>
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_preferences"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_look_n_feel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_title="@string/nc_look_and_feel" />
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_privacy_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_title="@string/nc_privacy" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
<com.yarolegovich.mp.MaterialPreferenceCategory
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:mpc_title="@string/nc_about"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_privacy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_title="@string/nc_privacy" />
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_source_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_title="@string/nc_get_source_code"/>
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_licence"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_summary="@string/nc_license_summary"
apc:mp_title="@string/nc_license_title"/>
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/settings_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_summary="v0.1"
apc:mp_title="@string/nc_app_name"/>
</com.yarolegovich.mp.MaterialPreferenceCategory>
</com.yarolegovich.mp.MaterialPreferenceScreen>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:paddingVertical="8dp"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/avatarImageView"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"
android:layout_centerVertical="true"
android:layout_marginHorizontal="8dp"
app:shapeAppearanceOverlay="@style/circleImageView"
tools:srcCompat="@tools:sample/avatars[1]" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/userDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/userMoreOptionsView"
android:layout_toEndOf="@id/avatarImageView"
android:ellipsize="end"
android:singleLine="true"
android:lines="1"
tools:text="Awesome user" />
<ImageView
android:id="@+id/userMoreOptionsView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_marginHorizontal="8dp"
android:layout_centerVertical="true"
android:scaleType="center"
android:src="@drawable/ic_baseline_more_vert_24" />
<ProgressBar
android:id="@+id/userProgressBar"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="12dp"
android:indeterminateTint="@color/colorPrimary"
android:visibility="gone" />
</RelativeLayout>

View File

@ -75,6 +75,7 @@
<string name="nc_settings_remove_confirmation">Please confirm your intent to remove the current account.</string>
<string name="nc_settings_remove_account">Remove account</string>
<string name="nc_settings_add_account">Add a new account</string>
<string name="nc_settings_new_account">New account</string>
<string name="nc_add">Add</string>
<string name="nc_settings_wrong_account">Only current account can be reauthorized</string>
<string name="nc_settings_no_talk_installed">Talk app is not installed on the server you tried to authenticate against</string>
@ -347,4 +348,8 @@
<string name="nc_reject_call">Reject</string>
<string name="silenced_by_moderator">You were silenced by a moderator</string>
<string name="nc_failed_to_send">Failed to send - tap to retry sending.</string>
<string name="nc_preferences">Preferences</string>
<string name="nc_look_and_feel">Look&amp;Feel</string>
<string name="nc_settings_calls_sound">Calls sound</string>
<string name="nc_settings_notifications_sound">Notifications sound</string>
</resources>