Add phone numbers to account

This commit is contained in:
Benoit Marty 2020-08-31 20:53:37 +02:00
parent 931eeac548
commit bf5c1e9d8f
11 changed files with 171 additions and 13 deletions

View File

@ -2,7 +2,7 @@ Changes in Element 1.0.6 (2020-XX-XX)
===================================================
Features ✨:
- List phone numbers and emails added to the Matrix account, and add Email to account (#44)
- List phone numbers and emails added to the Matrix account, and add Email adn phone numbers to account (#44, #45)
Improvements 🙌:
- You can now join room through permalink and within room directory search

View File

@ -99,6 +99,11 @@ interface ProfileService {
*/
fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
/**
* Validate a code received by text message
*/
fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable
/**
* Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
*/

View File

@ -46,6 +46,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
private val setDisplayNameTask: SetDisplayNameTask,
private val setAvatarUrlTask: SetAvatarUrlTask,
private val addThreePidTask: AddThreePidTask,
private val validateSmsCodeTask: ValidateSmsCodeTask,
private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask,
private val deleteThreePidTask: DeleteThreePidTask,
private val pendingThreePidMapper: PendingThreePidMapper,
@ -158,6 +159,14 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
.executeBy(taskExecutor)
}
override fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
return validateSmsCodeTask
.configureWith(ValidateSmsCodeTask.Params(threePid, code)) {
callback = matrixCallback
}
.executeBy(taskExecutor)
}
override fun finalizeAddingThreePid(threePid: ThreePid,
uiaSession: String?,
accountPassword: String?,

View File

@ -19,6 +19,8 @@
package org.matrix.android.sdk.internal.session.profile
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.registration.SuccessResult
import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
@ -26,6 +28,7 @@ import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Url
internal interface ProfileAPI {
/**
@ -83,6 +86,13 @@ internal interface ProfileAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken")
fun addMsisdn(@Body body: AddMsisdnBody): Call<AddMsisdnResponse>
/**
* Validate Msisdn code (same model than for Identity server API)
*/
@POST
fun validateMsisdn(@Url url: String,
@Body params: ValidationCodeBody): Call<SuccessResult>
/**
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add
*/

View File

@ -62,6 +62,9 @@ internal abstract class ProfileModule {
@Binds
abstract fun bindAddThreePidTask(task: DefaultAddThreePidTask): AddThreePidTask
@Binds
abstract fun bindValidateSmsCodeTask(task: DefaultValidateSmsCodeTask): ValidateSmsCodeTask
@Binds
abstract fun bindFinalizeAddingThreePidTask(task: DefaultFinalizeAddingThreePidTask): FinalizeAddingThreePidTask

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.profile
import com.zhuinden.monarchy.Monarchy
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.internal.auth.registration.SuccessResult
import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface ValidateSmsCodeTask : Task<ValidateSmsCodeTask.Params, Unit> {
data class Params(
val threePid: ThreePid.Msisdn,
val code: String
)
}
internal class DefaultValidateSmsCodeTask @Inject constructor(
private val profileAPI: ProfileAPI,
@SessionDatabase
private val monarchy: Monarchy,
private val pendingThreePidMapper: PendingThreePidMapper,
private val eventBus: EventBus
) : ValidateSmsCodeTask {
override suspend fun execute(params: ValidateSmsCodeTask.Params) {
// Search the pending ThreePid
val pendingThreePids = monarchy.fetchAllMappedSync(
{ it.where(PendingThreePidEntity::class.java) },
{ pendingThreePidMapper.map(it) }
)
.firstOrNull { it.threePid == params.threePid }
?: throw IllegalArgumentException("unknown threepid")
val url = pendingThreePids.submitUrl ?: throw IllegalArgumentException("invalid threepid")
val body = ValidationCodeBody(
clientSecret = pendingThreePids.clientSecret,
sid = pendingThreePids.sid,
code = params.code
)
val result = executeRequest<SuccessResult>(eventBus) {
apiCall = profileAPI.validateMsisdn(url, body)
}
if (!result.isSuccess()) {
throw Failure.SuccessError
}
}
}

View File

@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
sealed class ThreePidsSettingsAction : VectorViewModelAction {
data class ChangeState(val newState: ThreePidsSettingsState) : ThreePidsSettingsAction()
data class AddThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
data class SubmitCode(val threePid: ThreePid.Msisdn, val code: String) : ThreePidsSettingsAction()
data class ContinueThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
data class CancelThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
data class AccountPassword(val password: String) : ThreePidsSettingsAction()

View File

@ -50,6 +50,7 @@ class ThreePidsSettingsController @Inject constructor(
fun cancelAdding()
fun doAddEmail(email: String)
fun doAddMsisdn(msisdn: String)
fun submitCode(threePid: ThreePid.Msisdn, code: String)
fun continueThreePid(threePid: ThreePid)
fun cancelThreePid(threePid: ThreePid)
fun deleteThreePid(threePid: ThreePid)
@ -57,8 +58,12 @@ class ThreePidsSettingsController @Inject constructor(
var interactionListener: InteractionListener? = null
// For phone number or email (exclusive)
private var currentInputValue = ""
// For validation code
private val currentCodes = mutableMapOf<ThreePid, String>()
override fun buildModels(data: ThreePidsSettingsViewState?) {
if (data == null) return
@ -210,18 +215,38 @@ class ThreePidsSettingsController @Inject constructor(
title(threePid.getFormattedValue())
}
if (threePid is ThreePid.Email) {
settingsInformationItem {
id("info" + idPrefix + threePid.value)
message(stringProvider.getString(R.string.account_email_validation_message))
colorProvider(colorProvider)
when (threePid) {
is ThreePid.Email -> {
settingsInformationItem {
id("info" + idPrefix + threePid.value)
message(stringProvider.getString(R.string.account_email_validation_message))
colorProvider(colorProvider)
}
settingsContinueCancelItem {
id("cont" + idPrefix + threePid.value)
continueOnClick { interactionListener?.continueThreePid(threePid) }
cancelOnClick { interactionListener?.cancelThreePid(threePid) }
}
}
is ThreePid.Msisdn -> {
settingsInformationItem {
id("info" + idPrefix + threePid.value)
message(stringProvider.getString(R.string.settings_text_message_sent, threePid.getFormattedValue()))
colorProvider(colorProvider)
}
formEditTextItem {
id("msisdnVerification${threePid.value}")
inputType(InputType.TYPE_CLASS_NUMBER)
hint(stringProvider.getString(R.string.settings_text_message_sent_hint))
showBottomSeparator(false)
onTextChange { currentCodes[threePid] = it }
}
settingsContinueCancelItem {
id("cont" + idPrefix + threePid.value)
continueOnClick { interactionListener?.submitCode(threePid, currentCodes[threePid] ?: "") }
cancelOnClick { interactionListener?.cancelThreePid(threePid) }
}
}
}
settingsContinueCancelItem {
id("cont" + idPrefix + threePid.value)
continueOnClick { interactionListener?.continueThreePid(threePid) }
cancelOnClick { interactionListener?.cancelThreePid(threePid) }
}
}
}

View File

@ -132,6 +132,12 @@ class ThreePidsSettingsFragment @Inject constructor(
viewModel.handle(ThreePidsSettingsAction.AddThreePid(ThreePid.Msisdn(safeMsisdn)))
}
override fun submitCode(threePid: ThreePid.Msisdn, code: String) {
viewModel.handle(ThreePidsSettingsAction.SubmitCode(threePid, code))
// Hide the keyboard
view?.hideKeyboard()
}
override fun cancelAdding() {
viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.Idle))
// Hide the keyboard

View File

@ -131,6 +131,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
when (action) {
is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action)
is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action)
is ThreePidsSettingsAction.SubmitCode -> handleSubmitCode(action)
is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action)
is ThreePidsSettingsAction.AccountPassword -> handleAccountPassword(action)
is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action)
@ -138,6 +139,27 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
}.exhaustive
}
private fun handleSubmitCode(action: ThreePidsSettingsAction.SubmitCode) {
isLoading(true)
viewModelScope.launch {
// First submit the code
session.submitSmsCode(action.threePid, action.code, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// then finalize
pendingThreePid = action.threePid
session.finalizeAddingThreePid(action.threePid, null, null, loadingCallback)
}
override fun onFailure(failure: Throwable) {
// Wrong code?
isLoading(false)
_viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure))
}
})
}
}
private fun handleChangeState(action: ThreePidsSettingsAction.ChangeState) {
setState {
copy(
@ -152,7 +174,12 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
withState { state ->
val allThreePids = state.threePids.invoke().orEmpty() + state.pendingThreePids.invoke().orEmpty()
if (allThreePids.any { it.value == action.threePid.value }) {
_viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalArgumentException(stringProvider.getString(R.string.auth_email_already_defined))))
_viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalArgumentException(stringProvider.getString(
when (action.threePid) {
is ThreePid.Email -> R.string.auth_email_already_defined
is ThreePid.Msisdn -> R.string.auth_msisdn_already_defined
}
))))
} else {
viewModelScope.launch {
session.addThreePid(action.threePid, object : MatrixCallback<Unit> {

View File

@ -281,6 +281,7 @@
<string name="auth_invalid_email">"This doesnt look like a valid email address"</string>
<string name="auth_invalid_phone">"This doesnt look like a valid phone number"</string>
<string name="auth_email_already_defined">This email address is already defined.</string>
<string name="auth_msisdn_already_defined">This phone number is already defined.</string>
<string name="auth_missing_email">Missing email address</string>
<string name="auth_missing_phone">Missing phone number</string>
<string name="auth_missing_email_or_phone">Missing email address or phone number</string>
@ -1765,6 +1766,7 @@
<string name="settings_discovery_no_terms_title">Identity server has no terms of services</string>
<string name="settings_discovery_no_terms">The identity server you have chosen does not have any terms of services. Only continue if you trust the owner of the service</string>
<string name="settings_text_message_sent">A text message has been sent to %s. Please enter the verification code it contains.</string>
<string name="settings_text_message_sent_hint">Code</string>
<string name="settings_text_message_sent_wrong_code">The verification code is not correct.</string>
<string name="settings_discovery_disconnect_with_bound_pid">You are currently sharing email addresses or phone numbers on the identity server %1$s. You will need to reconnect to %2$s to stop sharing them.</string>