diff --git a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt index c73fa70388..ed3ae00567 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index fc913ff835..ea1841d870 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -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) } + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoveryPolicyItem.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoveryPolicyItem.kt new file mode 100644 index 0000000000..c97a2286ae --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoveryPolicyItem.kt @@ -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() { + + @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(R.id.discovery_policy_name) + val url by bind(R.id.discovery_policy_url) + } +} diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt index 426f1321e7..a2f855c499 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt index 440307ae51..d8c67592f1 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index 0e67b03f7c..6de7c1fba5 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt index 21fbcf1ca7..1d7346e463 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt @@ -21,10 +21,18 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized data class DiscoverySettingsState( - val identityServer: Async = Uninitialized, + val identityServer: Async = Uninitialized, val emailList: Async> = Uninitialized, val phoneNumbersList: Async> = 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 +) + +data class IdentityServerPolicy(val name: String, val url: String) diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 9d730d29cf..8b454607d5 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -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(initialState) { + private val session: Session, + private val stringProvider: StringProvider +) : VectorViewModel(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) = withState { state -> - if (state.identityServer().isNullOrBlank()) return@withState + if (state.identityServer()?.serverUrl.isNullOrBlank()) return@withState val emails = threePids.filterIsInstance() val msisdns = threePids.filterIsInstance() @@ -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) + } } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 55b74e9b34..debdf3739c 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -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)) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index c11b840522..612643c804 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/navigation/SettingsActivityPayload.kt b/vector/src/main/java/im/vector/app/features/navigation/SettingsActivityPayload.kt new file mode 100644 index 0000000000..0b128c51b1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/navigation/SettingsActivityPayload.kt @@ -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 +} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index a5df2ad1d4..07cd9d6dac 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -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" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt index 298551af2a..7cd4fc2d3d 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt @@ -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 if (isFirstCreation()) { // display the fragment - when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) { - EXTRA_DIRECT_ACCESS_GENERAL -> + + when (val payload = readPayload(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 } } - fun navigateTo(fragmentClass: Class) { + fun navigateTo(fragmentClass: Class, 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 private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment" } } + +private fun Activity.readPayload(default: T): T { + return intent.getParcelableExtra(KEY_ACTIVITY_PAYLOAD) ?: default +} + +private fun Intent.applyPayload(payload: T): Intent { + return putExtra(KEY_ACTIVITY_PAYLOAD, payload) +} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index f40079c615..51496d6eb0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -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(VectorPreferences.SETTINGS_DISCOVERY_PREFERENCE_KEY)!! + discoveryPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + (requireActivity() as VectorSettingsActivity).navigateTo( + DiscoverySettingsFragment::class.java, + SettingsActivityPayload.DiscoverySettings().toMvRxBundle() + ) + true + } + // Advanced settings // user account diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 5fdc316851..c1fd878013 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -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)) } + ) } } diff --git a/vector/src/main/res/layout/item_discovery_policy.xml b/vector/src/main/res/layout/item_discovery_policy.xml new file mode 100644 index 0000000000..1c03133111 --- /dev/null +++ b/vector/src/main/res/layout/item_discovery_policy.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index bb6f73812f..50306f1528 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2349,6 +2349,9 @@ Configure identity server Open Discovery Settings Change identity server + Show identity server policy + Hide identity server policy + No policy provided by the identity server You are currently using %1$s to discover and be discoverable by existing contacts you know. You are not currently using an identity server. To discover and be discoverable by existing contacts you know, configure one below. Discoverable email addresses @@ -2367,6 +2370,7 @@ Send emails and phone numbers 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. + Policy Enter an identity server URL Could not connect to identity server diff --git a/vector/src/main/res/xml/vector_settings_general.xml b/vector/src/main/res/xml/vector_settings_general.xml index 73ce09eb22..e5929d8933 100644 --- a/vector/src/main/res/xml/vector_settings_general.xml +++ b/vector/src/main/res/xml/vector_settings_general.xml @@ -28,10 +28,10 @@ app:fragment="im.vector.app.features.settings.threepids.ThreePidsSettingsFragment" /> + android:title="@string/settings_discovery_category" />