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 ed3ae00567..ad62b0fcb1 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 @@ -42,8 +42,8 @@ fun Context.displayInWebView(url: String) { 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 ?: "")) + .setTitle(getString(R.string.identity_server_consent_dialog_title_2, configuredIdentityServer ?: "")) + .setMessage(R.string.identity_server_consent_dialog_content_2) .setPositiveButton(R.string.yes) { _, _ -> consentCallBack.invoke() } 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 d8c67592f1..03c5d981cb 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 @@ -95,7 +95,7 @@ class DiscoverySettingsController @Inject constructor( } else { settingsInfoItem { id("idConsentInfo") - helperTextResId(R.string.settings_discovery_consent_notice_off) + helperTextResId(R.string.settings_discovery_consent_notice_off_2) } settingsButtonItem { id("idConsentButton") diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt index 83829c1119..86de26ac23 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt @@ -25,4 +25,5 @@ sealed class UserListAction : VectorViewModelAction { data class RemovePendingSelection(val pendingSelection: PendingSelection) : UserListAction() object ComputeMatrixToLinkForSharing : UserListAction() data class UpdateUserConsent(val consent: Boolean) : UserListAction() + object Resumed : UserListAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 147367c1da..2028e59073 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -146,7 +146,7 @@ class UserListController @Inject constructor(private val session: Session, text( span { span { - text = host.stringProvider.getString(R.string.settings_discovery_consent_notice_off) + text = host.stringProvider.getString(R.string.settings_discovery_consent_notice_off_2) } +"\n" span { 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 bb3caebafa..2b4d4f6f1e 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 @@ -225,6 +225,11 @@ class UserListFragment @Inject constructor( ) } + override fun onResume() { + super.onResume() + viewModel.handle(UserListAction.Resumed) + } + override fun giveIdentityServerConsent() { withState(viewModel) { state -> requireContext().showIdentityServerConsentDialog( diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 1187ace82e..0ef56084b7 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.userdirectory import androidx.lifecycle.asFlow +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted @@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull 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.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService @@ -55,7 +57,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val knownUsersSearch = MutableStateFlow("") private val directoryUsersSearch = MutableStateFlow("") - private val identityServerUsersSearch = MutableStateFlow("") + private val identityServerUsersSearch = MutableStateFlow(UserSearch(searchTerm = "")) @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -67,7 +69,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val identityServerListener = object : IdentityServiceListener { override fun onIdentityServerChange() { withState { - identityServerUsersSearch.tryEmit(it.searchTerm) + identityServerUsersSearch.tryEmit(UserSearch(it.searchTerm)) val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) setState { copy(configuredIdentityServer = identityServerURL) @@ -103,16 +105,29 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User is UserListAction.RemovePendingSelection -> handleRemoveSelectedUser(action) UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink() is UserListAction.UpdateUserConsent -> handleISUpdateConsent(action) + UserListAction.Resumed -> handleResumed() }.exhaustive } private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { session.identityService().setUserConsent(action.consent) withState { - identityServerUsersSearch.tryEmit(it.searchTerm) + retryUserSearch(it) } } + private fun handleResumed() { + withState { + if (it.hasNoIdentityServerConfigured()) { + retryUserSearch(it) + } + } + } + + private fun retryUserSearch(state: UserListViewState) { + identityServerUsersSearch.tryEmit(UserSearch(state.searchTerm, cacheBuster = System.currentTimeMillis())) + } + private fun handleSearchUsers(searchTerm: String) { setState { copy( @@ -128,7 +143,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User ) } } - identityServerUsersSearch.tryEmit(searchTerm) + identityServerUsersSearch.tryEmit(UserSearch(searchTerm)) knownUsersSearch.tryEmit(searchTerm) directoryUsersSearch.tryEmit(searchTerm) } @@ -142,7 +157,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleClearSearchUsers() { knownUsersSearch.tryEmit("") directoryUsersSearch.tryEmit("") - identityServerUsersSearch.tryEmit("") + identityServerUsersSearch.tryEmit(UserSearch("")) setState { copy(searchTerm = "") } @@ -150,10 +165,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun observeUsers() = withState { state -> identityServerUsersSearch - .filter { it.isEmail() } + .filter { it.searchTerm.isEmail() } .sample(300) .onEach { search -> - executeSearchEmail(search) + executeSearchEmail(search.searchTerm) }.launchIn(viewModelScope) knownUsersSearch @@ -175,11 +190,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private suspend fun executeSearchEmail(search: String) { suspend { val params = listOf(ThreePid.Email(search)) - val foundThreePid = tryOrNull { - session.identityService().lookUp(params).firstOrNull() - } + val foundThreePid = session.identityService().lookUp(params).firstOrNull() if (foundThreePid == null) { - null + ThreePidUser(email = search, user = null) } else { try { val json = session.getProfile(foundThreePid.matrixId) @@ -239,3 +252,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User setState { copy(pendingSelections = selections) } } } + +private fun UserListViewState.hasNoIdentityServerConfigured() = matchingEmail is Fail && matchingEmail.error == IdentityServiceError.NoIdentityServerConfigured + +/** + * Wrapper class to allow identical search terms to be re-emitted + */ +private data class UserSearch(val searchTerm: String, val cacheBuster: Long = 0) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f5fe801f65..429b2bb34c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2366,11 +2366,14 @@ Send emails and phone numbers You have given your consent to send emails and phone numbers to this identity server to discover other users from your contacts. You have not given your consent to send emails and phone numbers to this identity server to discover other users from your contacts. + Your contacts are private. To discover users from your contacts, we need your permission to send contact info to your identity server. Revoke my consent Give consent Send emails and phone numbers + Send emails and phone numbers to %s 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. + To discover existing contacts, you need to send contact info to your identity server.\n\nWe hash your data before sending for privacy. Do you consent to send this info? Policy Enter an identity server URL