diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt
index 3a5a5df284..09462b9b41 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt
@@ -40,15 +40,19 @@ interface IdentityService {
fun setNewIdentityServer(url: String?, callback: MatrixCallback): Cancelable
/**
- * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid,
- * and then the threePid will be associated with the matrix account
+ * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
*/
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
/**
- * This will perform the actual association of ThreePid and Matrix account
+ * This will cancel a pending binding of threePid.
*/
- fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+ fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
+ */
+ fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable
/**
* Submit the code that the identity server has sent to the user (in email or SMS)
@@ -58,6 +62,12 @@ interface IdentityService {
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback): Cancelable
/**
+ * This will perform the actual association of ThreePid and Matrix account
+ */
+ fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * Unbind a threePid
* The request will actually be done on the homeserver
*/
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
index ce3e555c66..0311cac64d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
@@ -123,7 +123,19 @@ internal class DefaultIdentityService @Inject constructor(
override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
- identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid))
+ identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
+ }
+ }
+
+ override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityServiceStore.deletePendingBinding(threePid)
+ }
+ }
+
+ override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt
index 1f7b2af701..e06a0e3b9a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt
@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.session.identity
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.identity.getCountryCode
import im.vector.matrix.android.internal.di.UserId
@@ -30,7 +31,9 @@ import javax.inject.Inject
internal interface IdentityRequestTokenForBindingTask : Task {
data class Params(
- val threePid: ThreePid
+ val threePid: ThreePid,
+ // True to request the identity server to send again the email or the SMS
+ val sendAgain: Boolean
)
}
@@ -45,6 +48,10 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
val pendingBindingEntity = identityServiceStore.getPendingBinding(params.threePid)
+ if (params.sendAgain && pendingBindingEntity == null) {
+ throw IdentityServiceError.NoCurrentBindingError
+ }
+
val clientSecret = pendingBindingEntity?.clientSecret ?: UUID.randomUUID().toString()
val sendAttempt = pendingBindingEntity?.sendAttempt?.inc() ?: 1
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingQuery.kt
index ffbc586c4e..e358be6bbb 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingQuery.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingQuery.kt
@@ -32,10 +32,7 @@ internal fun IdentityPendingBindingEntity.Companion.getOrCreate(realm: Realm, th
}
internal fun IdentityPendingBindingEntity.Companion.delete(realm: Realm, threePid: ThreePid) {
- realm.where()
- .equalTo(IdentityPendingBindingEntityFields.THREE_PID, threePid.toPrimaryKey())
- .findAll()
- .deleteAllFromRealm()
+ get(realm, threePid)?.deleteFromRealm()
}
internal fun IdentityPendingBindingEntity.Companion.deleteAll(realm: Realm) {
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt
index 30c571ed08..ca0ae787b0 100644
--- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt
@@ -21,6 +21,7 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
import com.google.i18n.phonenumbers.PhoneNumberUtil
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.identity.SharedState
@@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.error.ErrorFormatter
+import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import timber.log.Timber
@@ -111,15 +113,15 @@ class DiscoverySettingsController @Inject constructor(
colorProvider(colorProvider)
stringProvider(stringProvider)
when (piState.isShared) {
- is Loading -> buttonIndeterminate(true)
+ is Loading -> {
+ buttonIndeterminate(true)
+ }
is Fail -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonStyle(SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE)
buttonTitle(stringProvider.getString(R.string.global_retry))
infoMessage(piState.isShared.error.message)
- buttonClickListener(View.OnClickListener {
- listener?.onTapRetryToRetrieveBindings()
- })
+ buttonClickListener { listener?.onTapRetryToRetrieveBindings() }
}
is Success -> when (piState.isShared()) {
SharedState.SHARED,
@@ -143,8 +145,8 @@ class DiscoverySettingsController @Inject constructor(
}
when (piState.isShared()) {
SharedState.BINDING_IN_PROGRESS -> {
- val errorText = if (piState.isTokenSubmitted is Fail) {
- val error = piState.isTokenSubmitted.error
+ val errorText = if (piState.finalRequest is Fail) {
+ val error = piState.finalRequest.error
// Deal with error 500
//Ref: https://github.com/matrix-org/sydent/issues/292
if (error is Failure.ServerError
@@ -160,18 +162,22 @@ class DiscoverySettingsController @Inject constructor(
id("tverif" + piState.threePid.value)
descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
errorText(errorText)
- inProgress(piState.isTokenSubmitted is Loading)
+ inProgress(piState.finalRequest is Loading)
interactionListener(object : SettingsItemEditText.Listener {
override fun onValidate(code: String) {
if (piState.threePid is ThreePid.Msisdn) {
listener?.sendMsisdnVerificationCode(piState.threePid, code)
}
}
+
+ override fun onCancel() {
+ listener?.cancelBinding(piState.threePid)
+ }
})
}
}
else -> Unit
- }
+ }.exhaustive
}
}
}
@@ -210,15 +216,17 @@ class DiscoverySettingsController @Inject constructor(
colorProvider(colorProvider)
stringProvider(stringProvider)
when (piState.isShared) {
- is Loading -> buttonIndeterminate(true)
+ is Loading -> {
+ buttonIndeterminate(true)
+ showBottomButtons(false)
+ }
is Fail -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonStyle(SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE)
buttonTitle(stringProvider.getString(R.string.global_retry))
infoMessage(piState.isShared.error.message)
- buttonClickListener(View.OnClickListener {
- listener?.onTapRetryToRetrieveBindings()
- })
+ buttonClickListener { listener?.onTapRetryToRetrieveBindings() }
+ showBottomButtons(false)
}
is Success -> when (piState.isShared()) {
SharedState.SHARED,
@@ -235,14 +243,32 @@ class DiscoverySettingsController @Inject constructor(
}
SharedState.BINDING_IN_PROGRESS -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
- buttonTitleId(R.string._continue)
- infoMessageTintColorId(R.color.vector_info_color)
- infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
- buttonClickListener(View.OnClickListener {
+ buttonTitle(null)
+ showBottomButtons(true)
+ when (piState.finalRequest) {
+ is Uninitialized -> {
+ infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
+ infoMessageTintColorId(R.color.vector_info_color)
+ showBottomLoading(false)
+ }
+ is Loading -> {
+ infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
+ infoMessageTintColorId(R.color.vector_info_color)
+ showBottomLoading(true)
+ }
+ is Fail -> {
+ infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail_not_clicked, piState.threePid.value))
+ infoMessageTintColorId(R.color.riotx_destructive_accent)
+ showBottomLoading(false)
+ }
+ is Success -> Unit /* Cannot happen */
+ }
+ cancelClickListener { listener?.cancelBinding(piState.threePid) }
+ continueClickListener {
if (piState.threePid is ThreePid.Email) {
listener?.checkEmailVerification(piState.threePid)
}
- })
+ }
}
}
}
@@ -318,6 +344,7 @@ class DiscoverySettingsController @Inject constructor(
fun onTapRevoke(threePid: ThreePid)
fun onTapShare(threePid: ThreePid)
fun checkEmailVerification(threePid: ThreePid.Email)
+ fun cancelBinding(threePid: ThreePid)
fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
fun onTapChangeIdentityServer()
fun onTapDisconnectIdentityServer()
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt
index 9358b791c8..0c77c8d1a2 100644
--- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt
@@ -130,6 +130,10 @@ class DiscoverySettingsFragment @Inject constructor(
viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(threePid, code))
}
+ override fun cancelBinding(threePid: ThreePid) {
+ viewModel.handle(DiscoverySettingsAction.CancelBinding(threePid))
+ }
+
override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
//we should prompt if there are bound items with current is
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt
index dbb54b6dce..707d018412 100644
--- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt
@@ -44,7 +44,8 @@ data class PidInfo(
// Retrieved from IdentityServer, or transient state
val isShared: Async,
// Contains information about a current request to submit the token (for instance SMS code received by SMS)
- val isTokenSubmitted: Async = Uninitialized
+ // Or a current binding finalization, for email
+ val finalRequest: Async = Uninitialized
)
data class DiscoverySettingsState(
@@ -64,6 +65,7 @@ sealed class DiscoverySettingsAction : VectorViewModelAction {
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
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()
}
sealed class DiscoverySettingsViewEvents : VectorViewEvents {
@@ -133,6 +135,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action)
is DiscoverySettingsAction.SubmitMsisdnToken -> submitMsisdnToken(action)
+ is DiscoverySettingsAction.CancelBinding -> cancelBinding(action)
}.exhaustive
}
@@ -214,7 +217,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
emailList = Success(
currentMails.map {
if (it.threePid == threePid) {
- it.copy(isTokenSubmitted = submitState)
+ it.copy(finalRequest = submitState)
} else {
it
}
@@ -223,7 +226,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
phoneNumbersList = Success(
phones.map {
if (it.threePid == threePid) {
- it.copy(isTokenSubmitted = submitState)
+ it.copy(finalRequest = submitState)
} else {
it
}
@@ -274,6 +277,18 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
})
}
+ private fun cancelBinding(action: DiscoverySettingsAction.CancelBinding) {
+ identityService.cancelBindThreePid(action.threePid, object : MatrixCallback {
+ override fun onSuccess(data: Unit) {
+ changeThreePidState(action.threePid, Success(SharedState.NOT_SHARED))
+ }
+
+ override fun onFailure(failure: Throwable) {
+ // This could never fail
+ }
+ })
+ }
+
private fun startListenToIdentityManager() {
identityService.addListener(identityServerManagerListener)
}
@@ -362,25 +377,23 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun finalizeBind3pid(action: DiscoverySettingsAction.FinalizeBind3pid) = withState { state ->
val threePid = when (action.threePid) {
is ThreePid.Email -> {
- changeThreePidState(action.threePid, Loading())
state.emailList()?.find { it.threePid.value == action.threePid.email }?.threePid ?: return@withState
}
is ThreePid.Msisdn -> {
- changeThreePidState(action.threePid, Loading())
state.phoneNumbersList()?.find { it.threePid.value == action.threePid.msisdn }?.threePid ?: return@withState
}
}
+ changeThreePidSubmitState(action.threePid, Loading())
+
identityService.finalizeBindThreePid(threePid, object : MatrixCallback {
override fun onSuccess(data: Unit) {
+ changeThreePidSubmitState(action.threePid, Uninitialized)
changeThreePidState(action.threePid, Success(SharedState.SHARED))
}
override fun onFailure(failure: Throwable) {
- _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
-
- // Restore previous state after an error
- changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
+ changeThreePidSubmitState(action.threePid, Fail(failure))
}
})
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItemEditText.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItemEditText.kt
index 16a4b3ebcc..35c91989f8 100644
--- a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItemEditText.kt
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItemEditText.kt
@@ -49,6 +49,10 @@ abstract class SettingsItemEditText : EpoxyModelWithHolder(R.id.settings_item_edittext)
val textInputLayout by bind(R.id.settings_item_enter_til)
val validateButton by bindDiscoverable phone numbersWe sent you a confirm email to %s, check your email and click on the confirmation link
+ We sent you a confirm email to %s, please first check your email and click on the confirmation linkPendingEnter a new identity server