mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +03:00
Identity: validate code received by SMS
This commit is contained in:
parent
e962d1dadf
commit
e411f139c8
17 changed files with 177 additions and 45 deletions
|
@ -158,6 +158,9 @@ dependencies {
|
|||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
|
|
|
@ -23,9 +23,9 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
* Provides access to the identity server configuration and services identity server can provide
|
||||
*/
|
||||
interface IdentityService {
|
||||
|
||||
/**
|
||||
* Return the default identity server of the homeserver (using Wellknown request)
|
||||
* Return the default identity server of the homeserver (using Wellknown request).
|
||||
* It may be different from the current configured identity server
|
||||
*/
|
||||
fun getDefaultIdentityServer(callback: MatrixCallback<String?>): Cancelable
|
||||
|
||||
|
@ -51,9 +51,11 @@ interface IdentityService {
|
|||
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* @param code the code sent to the user phone number
|
||||
* Submit the code that the identity server has sent to the user (in email or SMS)
|
||||
* Once successful, you will have to call [finalizeBindThreePid]
|
||||
* @param code the code sent to the user
|
||||
*/
|
||||
fun submitValidationToken(pid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* The request will actually be done on the homeserver
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
||||
|
||||
sealed class ThreePid(open val value: String) {
|
||||
data class Email(val email: String) : ThreePid(email)
|
||||
data class Msisdn(val msisdn: String, val countryCode: String? = null) : ThreePid(msisdn)
|
||||
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
|
||||
}
|
||||
|
||||
internal fun ThreePid.toMedium(): String {
|
||||
|
@ -29,3 +31,10 @@ internal fun ThreePid.toMedium(): String {
|
|||
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(NumberParseException::class)
|
||||
internal fun ThreePid.Msisdn.getCountryCode(): String {
|
||||
return with(PhoneNumberUtil.getInstance()) {
|
||||
getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ internal class DefaultBulkLookupTask @Inject constructor(
|
|||
executeRequest(null) {
|
||||
apiCall = identityAPI.lookup(IdentityLookUpParams(
|
||||
hashedAddresses,
|
||||
"sha256",
|
||||
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
||||
hashDetailResponse.pepper
|
||||
))
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ internal class DefaultBulkLookupTask @Inject constructor(
|
|||
// Retrieve the new hash details
|
||||
val newHashDetailResponse = fetchAndStoreHashDetails(identityAPI)
|
||||
|
||||
if (hashDetailResponse.algorithms.contains("sha256").not()) {
|
||||
if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
|
||||
// TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
|
||||
// Also, what we have in cache is maybe outdated, the identity server maybe now support sha256
|
||||
throw IdentityServiceError.BulkLookupSha256NotSupported
|
||||
|
|
|
@ -71,6 +71,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val bindThreePidsTask: BindThreePidsTask,
|
||||
private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
|
||||
private val unbindThreePidsTask: UnbindThreePidsTask,
|
||||
private val identityApiProvider: IdentityApiProvider,
|
||||
private val accountDataDataSource: AccountDataDataSource
|
||||
|
@ -132,8 +133,10 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun submitValidationToken(pid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
TODO("Not yet implemented")
|
||||
override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.identity
|
||||
|
||||
import im.vector.matrix.android.internal.auth.registration.SuccessResult
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpParams
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpResponse
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
|
||||
|
@ -28,6 +30,7 @@ import retrofit2.Call
|
|||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/identity_service/latest
|
||||
|
@ -83,4 +86,13 @@ internal interface IdentityAPI {
|
|||
*/
|
||||
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken")
|
||||
fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call<IdentityRequestTokenResponse>
|
||||
|
||||
/**
|
||||
* Validate ownership of an email address, or a phone number.
|
||||
* Ref:
|
||||
* - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-msisdn-submittoken
|
||||
* - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken
|
||||
*/
|
||||
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
|
||||
fun submitToken(@Path("medium") medium: String, @Body body: IdentityRequestOwnershipParams): Call<SuccessResult>
|
||||
}
|
||||
|
|
|
@ -83,7 +83,6 @@ internal abstract class IdentityModule {
|
|||
.deleteRealmIfMigrationNeeded()
|
||||
.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@ -99,6 +98,9 @@ internal abstract class IdentityModule {
|
|||
@Binds
|
||||
abstract fun bindIdentityRequestTokenForBindingTask(task: DefaultIdentityRequestTokenForBindingTask): IdentityRequestTokenForBindingTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindIdentitySubmitTokenForBindingTask(task: DefaultIdentitySubmitTokenForBindingTask): IdentitySubmitTokenForBindingTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindBulkLookupTask(task: DefaultBulkLookupTask): BulkLookupTask
|
||||
|
||||
|
|
|
@ -18,6 +18,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
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityServiceStore
|
||||
|
@ -52,14 +53,16 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
|
|||
sendAttempt = 1,
|
||||
email = params.threePid.email
|
||||
))
|
||||
is ThreePid.Msisdn -> identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
|
||||
is ThreePid.Msisdn -> {
|
||||
identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = 1,
|
||||
phoneNumber = params.threePid.msisdn,
|
||||
countryCode = params.threePid.countryCode
|
||||
countryCode = params.threePid.getCountryCode()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenResponse.success) {
|
||||
throw IdentityServiceError.BindingError
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 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.toMedium
|
||||
import im.vector.matrix.android.internal.auth.registration.SuccessResult
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityServiceStore
|
||||
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface IdentitySubmitTokenForBindingTask : Task<IdentitySubmitTokenForBindingTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid,
|
||||
val token: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor(
|
||||
private val identityApiProvider: IdentityApiProvider,
|
||||
private val identityServiceStore: RealmIdentityServiceStore,
|
||||
@UserId private val userId: String
|
||||
) : IdentitySubmitTokenForBindingTask {
|
||||
|
||||
override suspend fun execute(params: IdentitySubmitTokenForBindingTask.Params) {
|
||||
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||
val pendingThreePid = identityServiceStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
|
||||
|
||||
val tokenResponse = executeRequest<SuccessResult>(null) {
|
||||
apiCall = identityAPI.submitToken(
|
||||
params.threePid.toMedium(),
|
||||
IdentityRequestOwnershipParams(
|
||||
clientSecret = pendingThreePid.clientSecret,
|
||||
sid = pendingThreePid.sid,
|
||||
token = params.token
|
||||
))
|
||||
}
|
||||
|
||||
if (!tokenResponse.isSuccess()) {
|
||||
throw IdentityServiceError.BindingError
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,4 +37,9 @@ internal data class IdentityHashDetailResponse(
|
|||
*/
|
||||
@Json(name = "algorithms")
|
||||
val algorithms: List<String>
|
||||
)
|
||||
) {
|
||||
companion object{
|
||||
const val ALGORITHM_SHA256 = "sha256"
|
||||
const val ALGORITHM_NONE = "none"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,21 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IdentityRequestOwnershipParams(
|
||||
/**
|
||||
* Required. The client secret that was supplied to the requestToken call.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
var clientSecret: String? = null,
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The session ID, generated by the requestToken call.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
var sid: String? = null,
|
||||
val sid: String,
|
||||
|
||||
/**
|
||||
* Required. The token generated by the requestToken call and sent to the user.
|
||||
*/
|
||||
@Json(name = "token")
|
||||
var token: String? = null
|
||||
val token: String
|
||||
)
|
||||
|
|
|
@ -21,7 +21,13 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
// Just to consider common parameters
|
||||
private interface IdentityRequestTokenBody {
|
||||
/**
|
||||
* Required. A unique string generated by the client, and used to identify the validation attempt.
|
||||
* It must be a string consisting of the characters [0-9a-zA-Z.=_-].
|
||||
* Its length must not exceed 255 characters and it must not be empty.
|
||||
*/
|
||||
val clientSecret: String
|
||||
|
||||
val sendAttempt: Int
|
||||
}
|
||||
|
||||
|
@ -30,9 +36,19 @@ internal data class IdentityRequestTokenForEmailBody(
|
|||
@Json(name = "client_secret")
|
||||
override val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The server will only send an email if the send_attempt is a number greater than the most
|
||||
* recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly
|
||||
* sending the same email in the case of request retries between the POSTing user and the identity server.
|
||||
* The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
|
||||
* If they do not, the server should respond with success but not resend the email.
|
||||
*/
|
||||
@Json(name = "send_attempt")
|
||||
override val sendAttempt: Int,
|
||||
|
||||
/**
|
||||
* Required. The email address to validate.
|
||||
*/
|
||||
@Json(name = "email")
|
||||
val email: String
|
||||
) : IdentityRequestTokenBody
|
||||
|
@ -42,12 +58,25 @@ internal data class IdentityRequestTokenForMsisdnBody(
|
|||
@Json(name = "client_secret")
|
||||
override val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The server will only send an SMS if the send_attempt is a number greater than the most recent one
|
||||
* which it has seen, scoped to that country + phone_number + client_secret triple. This is to avoid repeatedly
|
||||
* sending the same SMS in the case of request retries between the POSTing user and the identity server.
|
||||
* The client should increment this value if they desire a new SMS (e.g. a reminder) to be sent.
|
||||
*/
|
||||
@Json(name = "send_attempt")
|
||||
override val sendAttempt: Int,
|
||||
|
||||
/**
|
||||
* Required. The phone number to validate.
|
||||
*/
|
||||
@Json(name = "phone_number")
|
||||
val phoneNumber: String,
|
||||
|
||||
/**
|
||||
* Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in phone_number
|
||||
* should be parsed as if it were dialled from.
|
||||
*/
|
||||
@Json(name = "country")
|
||||
val countryCode: String?
|
||||
val countryCode: String
|
||||
) : IdentityRequestTokenBody
|
||||
|
|
|
@ -21,9 +21,17 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IdentityRequestTokenResponse(
|
||||
/**
|
||||
* Required. The session ID. Session IDs are opaque strings generated by the identity server.
|
||||
* They must consist entirely of the characters [0-9a-zA-Z.=_-].
|
||||
* Their length must not exceed 255 characters and they must not be empty.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
val sid: String,
|
||||
|
||||
/**
|
||||
* Not documented
|
||||
*/
|
||||
@Json(name = "success")
|
||||
val success: Boolean
|
||||
)
|
||||
|
|
|
@ -132,7 +132,7 @@ class DiscoverySettingsController @Inject constructor(
|
|||
}
|
||||
SharedState.BINDING_IN_PROGRESS -> {
|
||||
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
|
||||
buttonTitle("")
|
||||
buttonTitle(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ class DiscoverySettingsController @Inject constructor(
|
|||
interactionListener(object : SettingsItemText.Listener {
|
||||
override fun onValidate(code: String) {
|
||||
if (piState.threePid is ThreePid.Msisdn) {
|
||||
listener?.checkMsisdnVerification(piState.threePid, code)
|
||||
listener?.sendMsisdnVerificationCode(piState.threePid, code)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -299,7 +299,7 @@ class DiscoverySettingsController @Inject constructor(
|
|||
fun onTapRevoke(threePid: ThreePid)
|
||||
fun onTapShare(threePid: ThreePid)
|
||||
fun checkEmailVerification(threePid: ThreePid.Email)
|
||||
fun checkMsisdnVerification(threePid: ThreePid.Msisdn, code: String)
|
||||
fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
|
||||
fun onTapChangeIdentityServer()
|
||||
fun onTapDisconnectIdentityServer()
|
||||
fun onTapRetryToRetrieveBindings()
|
||||
|
|
|
@ -67,7 +67,7 @@ class DiscoverySettingsFragment @Inject constructor(
|
|||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DiscoverySettingsViewEvents.Failure -> {
|
||||
// TODO Snackbar.make(view, throwable.toString(), Snackbar.LENGTH_LONG).show()
|
||||
displayErrorDialog(it.throwable)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ class DiscoverySettingsFragment @Inject constructor(
|
|||
viewModel.handle(DiscoverySettingsAction.FinalizeBind3pid(threePid))
|
||||
}
|
||||
|
||||
override fun checkMsisdnVerification(threePid: ThreePid.Msisdn, code: String) {
|
||||
override fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String) {
|
||||
viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(threePid, code))
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
@ -166,18 +165,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
if (state.identityServer() == null) return@withState
|
||||
changeThreePidState(action.threePid, Loading())
|
||||
|
||||
val threePid = if (action.threePid is ThreePid.Msisdn && action.threePid.countryCode == null) {
|
||||
// Ensure we have a country code
|
||||
|
||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
||||
.parse("+${action.threePid.msisdn}", null)
|
||||
action.threePid.copy(countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
||||
)
|
||||
} else {
|
||||
action.threePid
|
||||
}
|
||||
|
||||
identityService.startBindThreePid(threePid, object : MatrixCallback<Unit> {
|
||||
identityService.startBindThreePid(action.threePid, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
|
||||
}
|
||||
|
@ -286,8 +274,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
override fun onSuccess(data: Map<ThreePid, SharedState>) {
|
||||
setState {
|
||||
copy(
|
||||
emailList = Success(toPidInfoList(data.filter { it.key is ThreePid.Email })),
|
||||
phoneNumbersList = Success(toPidInfoList(data.filter { it.key is ThreePid.Msisdn }))
|
||||
emailList = Success(data.filter { it.key is ThreePid.Email }.toPidInfoList()),
|
||||
phoneNumbersList = Success(data.filter { it.key is ThreePid.Msisdn }.toPidInfoList())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -312,8 +300,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
private fun toPidInfoList(threePidStatuses: Map<ThreePid, SharedState>): List<PidInfo> {
|
||||
return threePidStatuses.map { threePidStatus ->
|
||||
private fun Map<ThreePid, SharedState>.toPidInfoList(): List<PidInfo> {
|
||||
return map { threePidStatus ->
|
||||
PidInfo(
|
||||
threePid = threePidStatus.key,
|
||||
isShared = Success(threePidStatus.value)
|
||||
|
@ -328,7 +316,6 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
action.code,
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// TODO This should be done in the task
|
||||
finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(action.threePid))
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
android:background="?attr/colorBackgroundFloating"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="@dimen/layout_vertical_margin"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="@dimen/layout_vertical_margin">
|
||||
|
||||
|
|
Loading…
Reference in a new issue