Merge pull request #4184 from vector-im/feature/adm/is-policy

Always display identity server policies
This commit is contained in:
Benoit Marty 2021-10-07 20:53:03 +02:00 committed by GitHub
commit d6af355335
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 366 additions and 64 deletions

View file

@ -40,13 +40,16 @@ fun Context.displayInWebView(url: String) {
.show()
}
fun Context.showIdentityServerConsentDialog(configuredIdentityServer: String?, consentCallBack: (() -> Unit)) {
fun Context.showIdentityServerConsentDialog(configuredIdentityServer: String?, policyLinkCallback: () -> Unit, consentCallBack: (() -> Unit)) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.identity_server_consent_dialog_title)
.setMessage(getString(R.string.identity_server_consent_dialog_content, configuredIdentityServer ?: ""))
.setPositiveButton(R.string.yes) { _, _ ->
consentCallBack.invoke()
}
.setNeutralButton(R.string.identity_server_consent_dialog_neutral_policy) { _, _ ->
policyLinkCallback.invoke()
}
.setNegativeButton(R.string.no, null)
.show()
}

View file

@ -31,6 +31,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.showIdentityServerConsentDialog
import im.vector.app.databinding.FragmentContactsBookBinding
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.userdirectory.PendingSelection
import im.vector.app.features.userdirectory.UserListAction
import im.vector.app.features.userdirectory.UserListSharedAction
@ -74,9 +75,13 @@ class ContactsBookFragment @Inject constructor(
private fun setupConsentView() {
views.phoneBookSearchForMatrixContacts.setOnClickListener {
withState(contactsBookViewModel) { state ->
requireContext().showIdentityServerConsentDialog(state.identityServerUrl) {
contactsBookViewModel.handle(ContactsBookAction.UserConsentGranted)
}
requireContext().showIdentityServerConsentDialog(
state.identityServerUrl,
policyLinkCallback = {
navigator.openSettings(requireContext(), SettingsActivityPayload.DiscoverySettings(expandIdentityPolicies = true))
},
consentCallBack = { contactsBookViewModel.handle(ContactsBookAction.UserConsentGranted) }
)
}
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.discovery
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.onClick
@EpoxyModelClass(layout = R.layout.item_discovery_policy)
abstract class DiscoveryPolicyItem : EpoxyModelWithHolder<DiscoveryPolicyItem.Holder>() {
@EpoxyAttribute
var name: String? = null
@EpoxyAttribute
var url: String? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.title.text = name
holder.url.text = url
holder.view.onClick(clickListener)
}
class Holder : VectorEpoxyHolder() {
val title by bind<TextView>(R.id.discovery_policy_name)
val url by bind<TextView>(R.id.discovery_policy_url)
}
}

View file

@ -31,4 +31,5 @@ sealed class DiscoverySettingsAction : VectorViewModelAction {
data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
data class SubmitMsisdnToken(val threePid: ThreePid.Msisdn, val code: String) : DiscoverySettingsAction()
data class CancelBinding(val threePid: ThreePid) : DiscoverySettingsAction()
data class SetPoliciesExpandState(val expanded: Boolean) : DiscoverySettingsAction()
}

View file

@ -31,6 +31,7 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.getFormattedValue
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.form.formAdvancedToggleItem
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
@ -62,7 +63,7 @@ class DiscoverySettingsController @Inject constructor(
}
is Success -> {
buildIdentityServerSection(data)
val hasIdentityServer = data.identityServer().isNullOrBlank().not()
val hasIdentityServer = data.identityServer()?.serverUrl.isNullOrBlank().not()
if (hasIdentityServer && !data.termsNotSigned) {
buildConsentSection(data)
buildEmailsSection(data.emailList)
@ -106,7 +107,8 @@ class DiscoverySettingsController @Inject constructor(
}
private fun buildIdentityServerSection(data: DiscoverySettingsState) {
val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
val identityServer = data.identityServer()
val identityServerUrl = identityServer?.serverUrl ?: stringProvider.getString(R.string.none)
val host = this
settingsSectionTitleItem {
@ -116,28 +118,58 @@ class DiscoverySettingsController @Inject constructor(
settingsItem {
id("idServer")
title(identityServer)
title(identityServerUrl)
}
if (data.identityServer() != null && data.termsNotSigned) {
val policies = identityServer?.policies
if (policies != null) {
formAdvancedToggleItem {
id("policy-urls")
val titleRes = if (data.isIdentityPolicyUrlsExpanded) {
R.string.settings_discovery_hide_identity_server_policy_title
} else R.string.settings_discovery_show_identity_server_policy_title
title(host.stringProvider.getString(titleRes))
expanded(data.isIdentityPolicyUrlsExpanded)
listener { host.listener?.onPolicyUrlsExpandedStateToggled(!data.isIdentityPolicyUrlsExpanded) }
}
if (data.isIdentityPolicyUrlsExpanded) {
if (policies.isEmpty()) {
settingsInfoItem {
id("emptyPolicy")
helperText(host.stringProvider.getString(R.string.settings_discovery_no_policy_provided))
}
} else {
policies.forEach { policy ->
discoveryPolicyItem {
id(policy.url)
name(policy.name)
url(policy.url)
clickListener { host.listener?.onPolicyTapped(policy) }
}
}
}
}
}
if (identityServer != null && data.termsNotSigned) {
settingsInfoItem {
id("idServerFooter")
helperText(host.stringProvider.getString(R.string.settings_agree_to_terms, identityServer))
helperText(host.stringProvider.getString(R.string.settings_agree_to_terms, identityServerUrl))
showCompoundDrawable(true)
itemClickListener { host.listener?.openIdentityServerTerms() }
}
settingsButtonItem {
id("seeTerms")
colorProvider(host.colorProvider)
buttonTitle(host.stringProvider.getString(R.string.open_terms_of, identityServer))
buttonTitle(host.stringProvider.getString(R.string.open_terms_of, identityServerUrl))
buttonClickListener { host.listener?.openIdentityServerTerms() }
}
} else {
settingsInfoItem {
id("idServerFooter")
showCompoundDrawable(false)
if (data.identityServer() != null) {
helperText(host.stringProvider.getString(R.string.settings_discovery_identity_server_info, identityServer))
if (identityServer != null) {
helperText(host.stringProvider.getString(R.string.settings_discovery_identity_server_info, identityServerUrl))
} else {
helperTextResId(R.string.settings_discovery_identity_server_info_none)
}
@ -147,7 +179,7 @@ class DiscoverySettingsController @Inject constructor(
settingsButtonItem {
id("change")
colorProvider(host.colorProvider)
if (data.identityServer() == null) {
if (identityServer == null) {
buttonTitleId(R.string.add_identity_server)
} else {
buttonTitleId(R.string.change_identity_server)
@ -155,7 +187,7 @@ class DiscoverySettingsController @Inject constructor(
buttonClickListener { host.listener?.onTapChangeIdentityServer() }
}
if (data.identityServer() != null) {
if (identityServer != null) {
settingsInfoItem {
id("removeInfo")
helperTextResId(R.string.settings_discovery_disconnect_identity_server_info)
@ -400,5 +432,7 @@ class DiscoverySettingsController @Inject constructor(
fun onTapDisconnectIdentityServer()
fun onTapUpdateUserConsent(newValue: Boolean)
fun onTapRetryToRetrieveBindings()
fun onPolicyUrlsExpandedStateToggled(newExpandedState: Boolean)
fun onPolicyTapped(policy: IdentityServerPolicy)
}
}

View file

@ -21,6 +21,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -32,9 +33,11 @@ import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.core.utils.showIdentityServerConsentDialog
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.settings.VectorSettingsActivity
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
@ -52,6 +55,7 @@ class DiscoverySettingsFragment @Inject constructor(
}
private val viewModel by fragmentViewModel(DiscoverySettingsViewModel::class)
private val discoveryArgs: SettingsActivityPayload.DiscoverySettings by args()
lateinit var sharedViewModel: DiscoverySharedViewModel
@ -77,6 +81,9 @@ class DiscoverySettingsFragment @Inject constructor(
}
}.exhaustive
}
if (discoveryArgs.expandIdentityPolicies) {
viewModel.handle(DiscoverySettingsAction.SetPoliciesExpandState(expanded = true))
}
}
override fun onDestroyView() {
@ -111,7 +118,7 @@ class DiscoverySettingsFragment @Inject constructor(
requireContext(),
termsActivityResultLauncher,
TermsService.ServiceType.IdentityService,
state.identityServer()?.ensureProtocol() ?: "",
state.identityServer()?.serverUrl?.ensureProtocol() ?: "",
null)
}
}
@ -179,9 +186,11 @@ class DiscoverySettingsFragment @Inject constructor(
override fun onTapUpdateUserConsent(newValue: Boolean) {
if (newValue) {
withState(viewModel) { state ->
requireContext().showIdentityServerConsentDialog(state.identityServer.invoke()) {
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(true))
}
requireContext().showIdentityServerConsentDialog(
state.identityServer.invoke()?.serverUrl,
policyLinkCallback = { viewModel.handle(DiscoverySettingsAction.SetPoliciesExpandState(expanded = true)) },
consentCallBack = { viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(true)) }
)
}
} else {
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(false))
@ -192,6 +201,14 @@ class DiscoverySettingsFragment @Inject constructor(
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
}
override fun onPolicyUrlsExpandedStateToggled(newExpandedState: Boolean) {
viewModel.handle(DiscoverySettingsAction.SetPoliciesExpandState(expanded = newExpandedState))
}
override fun onPolicyTapped(policy: IdentityServerPolicy) {
openUrlInChromeCustomTab(requireContext(), null, policy.url)
}
private fun navigateToChangeIdentityServerFragment() {
(vectorBaseActivity as? VectorSettingsActivity)?.navigateTo(SetIdentityServerFragment::class.java)
}

View file

@ -21,10 +21,18 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
data class DiscoverySettingsState(
val identityServer: Async<String?> = Uninitialized,
val identityServer: Async<IdentityServerWithTerms?> = Uninitialized,
val emailList: Async<List<PidInfo>> = Uninitialized,
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
// Can be true if terms are updated
val termsNotSigned: Boolean = false,
val userConsent: Boolean = false
val userConsent: Boolean = false,
val isIdentityPolicyUrlsExpanded: Boolean = false
) : MvRxState
data class IdentityServerWithTerms(
val serverUrl: String,
val policies: List<IdentityServerPolicy>
)
data class IdentityServerPolicy(val name: String, val url: String)

View file

@ -27,20 +27,25 @@ import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureProtocol
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.rx.rx
class DiscoverySettingsViewModel @AssistedInject constructor(
@Assisted initialState: DiscoverySettingsState,
private val session: Session) :
VectorViewModel<DiscoverySettingsState, DiscoverySettingsAction, DiscoverySettingsViewEvents>(initialState) {
private val session: Session,
private val stringProvider: StringProvider
) : VectorViewModel<DiscoverySettingsState, DiscoverySettingsAction, DiscoverySettingsViewEvents>(initialState) {
@AssistedFactory
interface Factory {
@ -57,25 +62,32 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
private val identityService = session.identityService()
private val termsService: TermsService = session
private val identityServerManagerListener = object : IdentityServiceListener {
override fun onIdentityServerChange() = withState { state ->
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
val currentIS = state.identityServer()
setState {
copy(
identityServer = Success(identityServerUrl),
userConsent = identityService.getUserConsent()
viewModelScope.launch {
runCatching { fetchIdentityServerWithTerms() }.fold(
onSuccess = {
val currentIS = state.identityServer()
setState {
copy(
identityServer = Success(it),
userConsent = identityService.getUserConsent()
)
}
if (currentIS != it) retrieveBinding()
},
onFailure = { _viewEvents.post(DiscoverySettingsViewEvents.Failure(it)) }
)
}
if (currentIS != identityServerUrl) retrieveBinding()
}
}
init {
setState {
copy(
identityServer = Success(identityService.getCurrentIdentityServerUrl()),
identityServer = Success(identityService.getCurrentIdentityServerUrl()?.let { IdentityServerWithTerms(it, emptyList()) }),
userConsent = identityService.getUserConsent()
)
}
@ -99,16 +111,17 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
override fun handle(action: DiscoverySettingsAction) {
when (action) {
DiscoverySettingsAction.Refresh -> refreshPendingEmailBindings()
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
is DiscoverySettingsAction.UpdateUserConsent -> handleUpdateUserConsent(action)
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
is DiscoverySettingsAction.SubmitMsisdnToken -> submitMsisdnToken(action)
is DiscoverySettingsAction.CancelBinding -> cancelBinding(action)
DiscoverySettingsAction.Refresh -> fetchContent()
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
is DiscoverySettingsAction.SetPoliciesExpandState -> updatePolicyUrlsExpandedState(action.expanded)
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
is DiscoverySettingsAction.UpdateUserConsent -> handleUpdateUserConsent(action)
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
is DiscoverySettingsAction.SubmitMsisdnToken -> submitMsisdnToken(action)
is DiscoverySettingsAction.CancelBinding -> cancelBinding(action)
}.exhaustive
}
@ -135,6 +148,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
}
private fun updatePolicyUrlsExpandedState(isExpanded: Boolean) {
setState { copy(isIdentityPolicyUrlsExpanded = isExpanded) }
}
private fun changeIdentityServer(action: DiscoverySettingsAction.ChangeIdentityServer) {
setState { copy(identityServer = Loading()) }
@ -143,7 +160,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
val data = session.identityService().setNewIdentityServer(action.url)
setState {
copy(
identityServer = Success(data),
identityServer = Success(IdentityServerWithTerms(data, emptyList())),
userConsent = false
)
}
@ -287,7 +304,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
private fun retrieveBinding(threePids: List<ThreePid>) = withState { state ->
if (state.identityServer().isNullOrBlank()) return@withState
if (state.identityServer()?.serverUrl.isNullOrBlank()) return@withState
val emails = threePids.filterIsInstance<ThreePid.Email>()
val msisdns = threePids.filterIsInstance<ThreePid.Msisdn>()
@ -335,7 +352,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state ->
if (state.identityServer().isNullOrBlank()) return@withState
if (state.identityServer()?.serverUrl.isNullOrBlank()) return@withState
changeThreePidSubmitState(action.threePid, Loading())
@ -378,12 +395,37 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
}
private fun refreshPendingEmailBindings() = withState { state ->
private fun fetchContent() = withState { state ->
state.emailList()?.forEach { info ->
when (info.isShared()) {
SharedState.BINDING_IN_PROGRESS -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid), false)
else -> Unit
}
}
viewModelScope.launch {
runCatching { fetchIdentityServerWithTerms() }.fold(
onSuccess = { setState { copy(identityServer = Success(it)) } },
onFailure = { _viewEvents.post(DiscoverySettingsViewEvents.Failure(it)) }
)
}
}
private suspend fun fetchIdentityServerWithTerms(): IdentityServerWithTerms? {
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
return identityServerUrl?.let {
val terms = termsService.getTerms(TermsService.ServiceType.IdentityService, identityServerUrl.ensureProtocol())
.serverResponse
.getLocalizedTerms(stringProvider.getString(R.string.resources_language))
val policyUrls = terms.mapNotNull {
val name = it.localizedName ?: it.policyName
val url = it.localizedUrl
if (name == null || url == null) {
null
} else {
IdentityServerPolicy(name = name, url = url)
}
}
IdentityServerWithTerms(identityServerUrl, policyUrls)
}
}
}

View file

@ -353,6 +353,11 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent)
}
override fun openSettings(context: Context, payload: SettingsActivityPayload) {
val intent = VectorSettingsActivity.getIntent(context, payload)
context.startActivity(intent)
}
override fun openDebug(context: Context) {
context.startActivity(Intent(context, DebugMenuActivity::class.java))
}

View file

@ -83,6 +83,8 @@ interface Navigator {
fun openSettings(context: Context, directAccess: Int = VectorSettingsActivity.EXTRA_DIRECT_ACCESS_ROOT)
fun openSettings(context: Context, payload: SettingsActivityPayload)
fun openDebug(context: Context)
fun openKeysBackupSetup(context: Context, showManualExport: Boolean)

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.navigation
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed interface SettingsActivityPayload : Parcelable {
@Parcelize object Root : SettingsActivityPayload
@Parcelize object AdvancedSettings : SettingsActivityPayload
@Parcelize object SecurityPrivacy : SettingsActivityPayload
@Parcelize object SecurityPrivacyManageSessions : SettingsActivityPayload
@Parcelize object General : SettingsActivityPayload
@Parcelize object Notifications : SettingsActivityPayload
@Parcelize
data class DiscoverySettings(val expandIdentityPolicies: Boolean = false) : SettingsActivityPayload
}

View file

@ -44,6 +44,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
const val SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY"
const val SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY"
const val SETTINGS_DISCOVERY_PREFERENCE_KEY = "SETTINGS_DISCOVERY_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY"
const val SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY"

View file

@ -15,8 +15,12 @@
*/
package im.vector.app.features.settings
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.preference.Preference
@ -27,6 +31,7 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityVectorSettingsBinding
import im.vector.app.features.discovery.DiscoverySettingsFragment
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment
import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment
import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
@ -35,6 +40,8 @@ import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
private const val KEY_ACTIVITY_PAYLOAD = "settings-activity-payload"
/**
* Displays the client settings.
*/
@ -64,27 +71,28 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
if (isFirstCreation()) {
// display the fragment
when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) {
EXTRA_DIRECT_ACCESS_GENERAL ->
when (val payload = readPayload<SettingsActivityPayload>(SettingsActivityPayload.Root)) {
SettingsActivityPayload.General ->
replaceFragment(R.id.vector_settings_page, VectorSettingsGeneralFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS ->
SettingsActivityPayload.AdvancedSettings ->
replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY ->
SettingsActivityPayload.SecurityPrivacy ->
replaceFragment(R.id.vector_settings_page, VectorSettingsSecurityPrivacyFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS ->
SettingsActivityPayload.SecurityPrivacyManageSessions ->
replaceFragment(R.id.vector_settings_page,
VectorSettingsDevicesFragment::class.java,
null,
FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_NOTIFICATIONS -> {
SettingsActivityPayload.Notifications -> {
requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
replaceFragment(R.id.vector_settings_page, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG)
}
EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS -> {
replaceFragment(R.id.vector_settings_page, DiscoverySettingsFragment::class.java, null, FRAGMENT_TAG)
is SettingsActivityPayload.DiscoverySettings -> {
Log.e("!!!", "SettingsActivityPayload.DiscoverySettings : $payload")
replaceFragment(R.id.vector_settings_page, DiscoverySettingsFragment::class.java, payload, FRAGMENT_TAG)
}
else ->
else ->
replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG)
}
}
@ -148,19 +156,31 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
}
}
fun <T : Fragment> navigateTo(fragmentClass: Class<T>) {
fun <T : Fragment> navigateTo(fragmentClass: Class<T>, arguments: Bundle? = null) {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)
.replace(R.id.vector_settings_page, fragmentClass, null)
.replace(R.id.vector_settings_page, fragmentClass, arguments)
.addToBackStack(null)
.commit()
}
companion object {
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }
fun getIntent(context: Context, directAccess: Int) = Companion.getIntent(context, when (directAccess) {
EXTRA_DIRECT_ACCESS_ROOT -> SettingsActivityPayload.Root
EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS -> SettingsActivityPayload.AdvancedSettings
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY -> SettingsActivityPayload.SecurityPrivacy
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS -> SettingsActivityPayload.SecurityPrivacyManageSessions
EXTRA_DIRECT_ACCESS_GENERAL -> SettingsActivityPayload.General
EXTRA_DIRECT_ACCESS_NOTIFICATIONS -> SettingsActivityPayload.Notifications
EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS -> SettingsActivityPayload.DiscoverySettings()
else -> {
Timber.w("Unknown directAccess: $directAccess defaulting to Root")
SettingsActivityPayload.Root
}
})
private const val EXTRA_DIRECT_ACCESS = "EXTRA_DIRECT_ACCESS"
fun getIntent(context: Context, payload: SettingsActivityPayload) = Intent(context, VectorSettingsActivity::class.java)
.applyPayload(payload)
const val EXTRA_DIRECT_ACCESS_ROOT = 0
const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1
@ -173,3 +193,11 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
}
}
private fun <T : Parcelable> Activity.readPayload(default: T): T {
return intent.getParcelableExtra(KEY_ACTIVITY_PAYLOAD) ?: default
}
private fun <T : Parcelable> Intent.applyPayload(payload: T): Intent {
return putExtra(KEY_ACTIVITY_PAYLOAD, payload)
}

View file

@ -38,6 +38,7 @@ import im.vector.app.R
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.core.preference.UserAvatarPreference
@ -50,6 +51,8 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogChangePasswordBinding
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.discovery.DiscoverySettingsFragment
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.workers.signout.SignOutUiWorker
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.Dispatchers
@ -173,6 +176,15 @@ class VectorSettingsGeneralFragment @Inject constructor(
mPasswordPreference.isVisible = false
}
val discoveryPreference = findPreference<VectorPreference>(VectorPreferences.SETTINGS_DISCOVERY_PREFERENCE_KEY)!!
discoveryPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
(requireActivity() as VectorSettingsActivity).navigateTo(
DiscoverySettingsFragment::class.java,
SettingsActivityPayload.DiscoverySettings().toMvRxBundle()
)
true
}
// Advanced settings
// user account

View file

@ -43,6 +43,7 @@ import im.vector.app.core.utils.showIdentityServerConsentDialog
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentUserListBinding
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.settings.VectorSettingsActivity
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User
@ -227,9 +228,13 @@ class UserListFragment @Inject constructor(
override fun giveIdentityServerConsent() {
withState(viewModel) { state ->
requireContext().showIdentityServerConsentDialog(state.configuredIdentityServer) {
viewModel.handle(UserListAction.UpdateUserConsent(true))
}
requireContext().showIdentityServerConsentDialog(
state.configuredIdentityServer,
policyLinkCallback = {
navigator.openSettings(requireContext(), SettingsActivityPayload.DiscoverySettings(expandIdentityPolicies = true))
},
consentCallBack = { viewModel.handle(UserListAction.UpdateUserConsent(true)) }
)
}
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="16dp">
<TextView
android:id="@+id/discovery_policy_name"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/discovery_policy_arrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Integration manager" />
<TextView
android:id="@+id/discovery_policy_url"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:textColor="?vctr_content_secondary"
app:layout_constraintEnd_toStartOf="@id/discovery_policy_arrow"
app:layout_constraintStart_toStartOf="@+id/discovery_policy_name"
app:layout_constraintTop_toBottomOf="@+id/discovery_policy_name"
tools:text="Use bots, bridges, widget and sticker packs." />
<!-- Do not use drawableEnd on the TextView because of RTL support -->
<ImageView
android:id="@+id/discovery_policy_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:rotationY="@integer/rtl_mirror_flip"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2349,6 +2349,9 @@
<string name="add_identity_server">Configure identity server</string>
<string name="open_discovery_settings">Open Discovery Settings</string>
<string name="change_identity_server">Change identity server</string>
<string name="settings_discovery_show_identity_server_policy_title">Show identity server policy</string>
<string name="settings_discovery_hide_identity_server_policy_title">Hide identity server policy</string>
<string name="settings_discovery_no_policy_provided">No policy provided by the identity server</string>
<string name="settings_discovery_identity_server_info">You are currently using %1$s to discover and be discoverable by existing contacts you know.</string>
<string name="settings_discovery_identity_server_info_none">You are not currently using an identity server. To discover and be discoverable by existing contacts you know, configure one below.</string>
<string name="settings_discovery_emails_title">Discoverable email addresses</string>
@ -2367,6 +2370,7 @@
<string name="identity_server_consent_dialog_title">Send emails and phone numbers</string>
<string name="identity_server_consent_dialog_content">In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured identity server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.</string>
<string name="identity_server_consent_dialog_neutral_policy">Policy</string>
<string name="settings_discovery_enter_identity_server">Enter an identity server URL</string>
<string name="settings_discovery_bad_identity_server">Could not connect to identity server</string>

View file

@ -28,10 +28,10 @@
app:fragment="im.vector.app.features.settings.threepids.ThreePidsSettingsFragment" />
<im.vector.app.core.preference.VectorPreference
android:key="SETTINGS_DISCOVERY_PREFERENCE_KEY"
android:persistent="false"
android:summary="@string/settings_discovery_manage"
android:title="@string/settings_discovery_category"
app:fragment="im.vector.app.features.discovery.DiscoverySettingsFragment" />
android:title="@string/settings_discovery_category" />
</im.vector.app.core.preference.VectorPreferenceCategory>