Ask for explicit user consent to send their contact details to the identity server (#2375)
This commit is contained in:
parent
99bea8f7c3
commit
6020f423f4
@ -6,6 +6,7 @@ Features ✨:
|
|||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Open an existing DM instead of creating a new one (#2319)
|
- Open an existing DM instead of creating a new one (#2319)
|
||||||
|
- Ask for explicit user consent to send their contact details to the identity server (#2375)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix issue when restoring draft after sharing (#2287)
|
- Fix issue when restoring draft after sharing (#2287)
|
||||||
|
@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||||||
sealed class ContactsBookAction : VectorViewModelAction {
|
sealed class ContactsBookAction : VectorViewModelAction {
|
||||||
data class FilterWith(val filter: String) : ContactsBookAction()
|
data class FilterWith(val filter: String) : ContactsBookAction()
|
||||||
data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction()
|
data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction()
|
||||||
|
object UserConsentGranted : ContactsBookAction()
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.contactsbook
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
@ -57,10 +58,26 @@ class ContactsBookFragment @Inject constructor(
|
|||||||
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupFilterView()
|
setupFilterView()
|
||||||
|
setupConsentView()
|
||||||
setupOnlyBoundContactsView()
|
setupOnlyBoundContactsView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupConsentView() {
|
||||||
|
phoneBookSearchForMatrixContacts.setOnClickListener {
|
||||||
|
withState(contactsBookViewModel) { state ->
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.identity_server_consent_dialog_title)
|
||||||
|
.setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServerUrl ?: ""))
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
contactsBookViewModel.handle(ContactsBookAction.UserConsentGranted)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupOnlyBoundContactsView() {
|
private fun setupOnlyBoundContactsView() {
|
||||||
phoneBookOnlyBoundContacts.checkedChanges()
|
phoneBookOnlyBoundContacts.checkedChanges()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
@ -98,6 +115,7 @@ class ContactsBookFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(contactsBookViewModel) { state ->
|
override fun invalidate() = withState(contactsBookViewModel) { state ->
|
||||||
|
phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
|
||||||
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
|
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
|
||||||
contactsBookController.setData(state)
|
contactsBookController.setData(state)
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,10 @@ import kotlinx.coroutines.launch
|
|||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
||||||
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private typealias PhoneBookSearch = String
|
|
||||||
|
|
||||||
class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: ContactsBookViewState,
|
initialState: ContactsBookViewState,
|
||||||
private val contactsDataSource: ContactsDataSource,
|
private val contactsDataSource: ContactsDataSource,
|
||||||
@ -85,7 +84,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
|||||||
private fun loadContacts() {
|
private fun loadContacts() {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
mappedContacts = Loading()
|
mappedContacts = Loading(),
|
||||||
|
identityServerUrl = session.identityService().getCurrentIdentityServerUrl(),
|
||||||
|
userConsent = session.identityService().getUserConsent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +110,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun performLookup(data: List<MappedContact>) {
|
private fun performLookup(data: List<MappedContact>) {
|
||||||
|
if (!session.identityService().getUserConsent()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val threePids = data.flatMap { contact ->
|
val threePids = data.flatMap { contact ->
|
||||||
contact.emails.map { ThreePid.Email(it.email) } +
|
contact.emails.map { ThreePid.Email(it.email) } +
|
||||||
@ -116,8 +120,14 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
|||||||
}
|
}
|
||||||
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
|
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// Ignore
|
|
||||||
Timber.w(failure, "Unable to perform the lookup")
|
Timber.w(failure, "Unable to perform the lookup")
|
||||||
|
|
||||||
|
// Should not happen, but just to be sure
|
||||||
|
if (failure is IdentityServiceError.UserConsentNotProvided) {
|
||||||
|
setState {
|
||||||
|
copy(userConsent = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSuccess(data: List<FoundThreePid>) {
|
override fun onSuccess(data: List<FoundThreePid>) {
|
||||||
@ -171,9 +181,21 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
|||||||
when (action) {
|
when (action) {
|
||||||
is ContactsBookAction.FilterWith -> handleFilterWith(action)
|
is ContactsBookAction.FilterWith -> handleFilterWith(action)
|
||||||
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
|
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
|
||||||
|
ContactsBookAction.UserConsentGranted -> handleUserConsentGranted()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleUserConsentGranted() {
|
||||||
|
session.identityService().setUserConsent(true)
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(userConsent = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the lookup
|
||||||
|
performLookup(allContacts)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {
|
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -26,10 +26,14 @@ data class ContactsBookViewState(
|
|||||||
val mappedContacts: Async<List<MappedContact>> = Loading(),
|
val mappedContacts: Async<List<MappedContact>> = Loading(),
|
||||||
// Use to filter contacts by display name
|
// Use to filter contacts by display name
|
||||||
val searchTerm: String = "",
|
val searchTerm: String = "",
|
||||||
// Tru to display only bound contacts with their bound 2pid
|
// True to display only bound contacts with their bound 2pid
|
||||||
val onlyBoundContacts: Boolean = false,
|
val onlyBoundContacts: Boolean = false,
|
||||||
// All contacts, filtered by searchTerm and onlyBoundContacts
|
// All contacts, filtered by searchTerm and onlyBoundContacts
|
||||||
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
||||||
// True when the identity service has return some data
|
// True when the identity service has return some data
|
||||||
val isBoundRetrieved: Boolean = false
|
val isBoundRetrieved: Boolean = false,
|
||||||
|
// The current identity server url if any
|
||||||
|
val identityServerUrl: String? = null,
|
||||||
|
// User consent to perform lookup (send emails to the identity server)
|
||||||
|
val userConsent: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
@ -93,6 +93,27 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/phoneBookSearchForMatrixContacts"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:text="@string/phone_book_perform_lookup"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/phoneBookBottomBarrier"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="phoneBookSearchForMatrixContacts,phoneBookOnlyBoundContacts" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/phoneBookFilterDivider"
|
android:id="@+id/phoneBookFilterDivider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -101,7 +122,7 @@
|
|||||||
android:background="?attr/vctr_list_divider_color"
|
android:background="?attr/vctr_list_divider_color"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/phoneBookOnlyBoundContacts" />
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookBottomBarrier" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/phoneBookRecyclerView"
|
android:id="@+id/phoneBookRecyclerView"
|
||||||
|
@ -2602,6 +2602,7 @@
|
|||||||
<string name="loading_contact_book">Retrieving your contacts…</string>
|
<string name="loading_contact_book">Retrieving your contacts…</string>
|
||||||
<string name="empty_contact_book">Your contact book is empty</string>
|
<string name="empty_contact_book">Your contact book is empty</string>
|
||||||
<string name="contacts_book_title">Contacts book</string>
|
<string name="contacts_book_title">Contacts book</string>
|
||||||
|
<string name="phone_book_perform_lookup">Search for contacts on Matrix</string>
|
||||||
|
|
||||||
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
||||||
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user