From bf5c1e9d8f090010de3f58756b389f393efa6151 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 31 Aug 2020 20:53:37 +0200 Subject: [PATCH] Add phone numbers to account --- CHANGES.md | 2 +- .../sdk/api/session/profile/ProfileService.kt | 5 ++ .../session/profile/DefaultProfileService.kt | 9 +++ .../internal/session/profile/ProfileAPI.kt | 10 +++ .../internal/session/profile/ProfileModule.kt | 3 + .../session/profile/ValidateSmsCodeTask.kt | 70 +++++++++++++++++++ .../threepids/ThreePidsSettingsAction.kt | 1 + .../threepids/ThreePidsSettingsController.kt | 47 ++++++++++--- .../threepids/ThreePidsSettingsFragment.kt | 6 ++ .../threepids/ThreePidsSettingsViewModel.kt | 29 +++++++- vector/src/main/res/values/strings.xml | 2 + 11 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt diff --git a/CHANGES.md b/CHANGES.md index b96337097b..9bd7a8be6b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index 5033a498d0..8742c9e7d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -99,6 +99,11 @@ interface ProfileService { */ fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable + /** + * Validate a code received by text message + */ + fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback): Cancelable + /** * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index b2f4ba0878..97212a8687 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -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): Cancelable { + return validateSmsCodeTask + .configureWith(ValidateSmsCodeTask.Params(threePid, code)) { + callback = matrixCallback + } + .executeBy(taskExecutor) + } + override fun finalizeAddingThreePid(threePid: ThreePid, uiaSession: String?, accountPassword: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index cfd965e23f..4e2f518c5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -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 + /** + * Validate Msisdn code (same model than for Identity server API) + */ + @POST + fun validateMsisdn(@Url url: String, + @Body params: ValidationCodeBody): Call + /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt index baeaf9fd58..ae7ae7a6f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt new file mode 100644 index 0000000000..b11955b96a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt @@ -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 { + 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(eventBus) { + apiCall = profileAPI.validateMsisdn(url, body) + } + + if (!result.isSuccess()) { + throw Failure.SuccessError + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt index 1dac082b63..d3df4b6e75 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt @@ -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() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt index 06a3a87421..51121d27e5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt @@ -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() + 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) } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index 8ca506e819..85512b61ab 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index f0bb0e9e41..9f7a201c2d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -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 { + 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 { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1e14e0b78a..74a60a969d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -281,6 +281,7 @@ "This doesn’t look like a valid email address" "This doesn’t look like a valid phone number" This email address is already defined. + This phone number is already defined. Missing email address Missing phone number Missing email address or phone number @@ -1765,6 +1766,7 @@ Identity server has no terms of services The identity server you have chosen does not have any terms of services. Only continue if you trust the owner of the service A text message has been sent to %s. Please enter the verification code it contains. + Code The verification code is not correct. 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.