mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-01 19:56:47 +01:00
List phone numbers and emails added to the Matrix account, and add Email to account (#44)
This commit is contained in:
parent
46d3608ccb
commit
175a5ab824
@ -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)
|
||||
|
||||
Improvements 🙌:
|
||||
- You can now join room through permalink and within room directory search
|
||||
|
@ -12,7 +12,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
### The email is already adding to an account
|
||||
### The email is already added to an account
|
||||
|
||||
400
|
||||
|
||||
@ -84,6 +84,8 @@ User clicks on CONTINUE
|
||||
|
||||
POST https://homeserver.org/_matrix/client/r0/account/3pid/add
|
||||
|
||||
TODO: Remove "identifier"?
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "bxyDHuJKsdkjMlTJ",
|
||||
|
@ -18,9 +18,13 @@
|
||||
package org.matrix.android.sdk.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
@ -43,10 +47,6 @@ import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
@ -110,6 +110,11 @@ class RxSession(private val session: Session) {
|
||||
.startWithCallable { session.getThreePids() }
|
||||
}
|
||||
|
||||
fun livePendingThreePIds(): Observable<List<ThreePid>> {
|
||||
return session.getPendingThreePidsLive().asObservable()
|
||||
.startWithCallable { session.getPendingThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
}
|
||||
|
@ -83,4 +83,32 @@ interface ProfileService {
|
||||
* @param refreshData set to true to fetch data from the homeserver
|
||||
*/
|
||||
fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
|
||||
|
||||
/**
|
||||
* Get the pending 3Pids, i.e. ThreePids that have requested a token, but not yet validated by the user.
|
||||
*/
|
||||
fun getPendingThreePids(): List<ThreePid>
|
||||
|
||||
/**
|
||||
* Get the pending 3Pids Live
|
||||
*/
|
||||
fun getPendingThreePidsLive(): LiveData<List<ThreePid>>
|
||||
|
||||
/**
|
||||
* Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list.
|
||||
*/
|
||||
fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
|
||||
*/
|
||||
fun finalizeAddingThreePid(threePid: ThreePid,
|
||||
uiaSession: String?,
|
||||
accountPassword: String?,
|
||||
matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Delete a 3Pids.
|
||||
*/
|
||||
fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package org.matrix.android.sdk.internal.database
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -32,6 +33,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
@ -63,4 +65,17 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo4(realm: DynamicRealm) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
realm.schema.create("PendingThreePidEntity")
|
||||
.addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingThreePidEntityFields.EMAIL, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.MSISDN, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.SEND_ATTEMPT, true)
|
||||
.addField(PendingThreePidEntityFields.SID, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.SID, true)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
|
||||
/**
|
||||
* This class is used to store pending threePid data, when user wants to add a threePid to his account
|
||||
*/
|
||||
internal open class PendingThreePidEntity(
|
||||
var email: String? = null,
|
||||
var msisdn: String? = null,
|
||||
var clientSecret: String = "",
|
||||
var sendAttempt: Int = 0,
|
||||
var sid: String = ""
|
||||
) : RealmObject()
|
@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule
|
||||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
PendingThreePidEntity::class,
|
||||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddEmailBody(
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The email address to validate.
|
||||
*/
|
||||
@Json(name = "email")
|
||||
val email: 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")
|
||||
val sendAttempt: Int
|
||||
)
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddThreePidResponse(
|
||||
/**
|
||||
* Required. The session ID. Session IDs are opaque strings that 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
|
||||
)
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.session.identity.ThreePid
|
||||
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 org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class AddThreePidTask : Task<AddThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultAddThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
private val eventBus: EventBus) : AddThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
val clientSecret = UUID.randomUUID().toString()
|
||||
val sendAttempt = 1
|
||||
val result = when (params.threePid) {
|
||||
is ThreePid.Email ->
|
||||
executeRequest<AddThreePidResponse>(eventBus) {
|
||||
val body = AddEmailBody(
|
||||
email = params.threePid.email,
|
||||
sendAttempt = sendAttempt,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
apiCall = profileAPI.addEmail(body)
|
||||
}
|
||||
is ThreePid.Msisdn -> TODO()
|
||||
}
|
||||
|
||||
// Store as a pending three pid
|
||||
monarchy.awaitTransaction { realm ->
|
||||
PendingThreePid(
|
||||
threePid = params.threePid,
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
sid = result.sid
|
||||
)
|
||||
.let { pendingThreePidMapper.map(it) }
|
||||
.let { realm.copyToRealm(it) }
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||
@ -44,6 +45,10 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||
private val getProfileInfoTask: GetProfileInfoTask,
|
||||
private val setDisplayNameTask: SetDisplayNameTask,
|
||||
private val setAvatarUrlTask: SetAvatarUrlTask,
|
||||
private val addThreePidTask: AddThreePidTask,
|
||||
private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask,
|
||||
private val deleteThreePidTask: DeleteThreePidTask,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
private val fileUploader: FileUploader) : ProfileService {
|
||||
|
||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
@ -116,9 +121,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||
override fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>> {
|
||||
if (refreshData) {
|
||||
// Force a refresh of the values
|
||||
refreshUserThreePidsTask
|
||||
.configureWith()
|
||||
.executeBy(taskExecutor)
|
||||
refreshThreePids()
|
||||
}
|
||||
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
@ -126,6 +129,69 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||
{ it.asDomain() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun refreshThreePids() {
|
||||
refreshUserThreePidsTask
|
||||
.configureWith()
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getPendingThreePids(): List<ThreePid> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ it.where<PendingThreePidEntity>() },
|
||||
{ pendingThreePidMapper.map(it).threePid }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPendingThreePidsLive(): LiveData<List<ThreePid>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ it.where<PendingThreePidEntity>() },
|
||||
{ pendingThreePidMapper.map(it).threePid }
|
||||
)
|
||||
}
|
||||
|
||||
override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return addThreePidTask
|
||||
.configureWith(AddThreePidTask.Params(threePid)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun finalizeAddingThreePid(threePid: ThreePid,
|
||||
uiaSession: String?,
|
||||
accountPassword: String?,
|
||||
matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return finalizeAddingThreePidTask
|
||||
.configureWith(FinalizeAddingThreePidTask.Params(threePid, uiaSession, accountPassword)) {
|
||||
callback = alsoRefresh(matrixCallback)
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the callback to fetch 3Pids from the server in case of success
|
||||
*/
|
||||
private fun alsoRefresh(callback: MatrixCallback<Unit>): MatrixCallback<Unit> {
|
||||
return object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
refreshThreePids()
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return deleteThreePidTask
|
||||
.configureWith(DeleteThreePidTask.Params(threePid)) {
|
||||
callback = alsoRefresh(matrixCallback)
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserThreePidEntity.asDomain(): ThreePid {
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DeleteThreePidBody(
|
||||
/**
|
||||
* Required. The medium of the third party identifier being removed. One of: ["email", "msisdn"]
|
||||
*/
|
||||
@Json(name = "medium") val medium: String,
|
||||
/**
|
||||
* Required. The third party address being removed.
|
||||
*/
|
||||
@Json(name = "address") val address: String
|
||||
)
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DeleteThreePidResponse(
|
||||
/**
|
||||
* Required. An indicator as to whether or not the homeserver was able to unbind the 3PID from
|
||||
* the identity server. success indicates that the identity server has unbound the identifier
|
||||
* whereas no-support indicates that the identity server refuses to support the request or the
|
||||
* homeserver was not able to determine an identity server to unbind from. One of: ["no-support", "success"]
|
||||
*/
|
||||
@Json(name = "id_server_unbind_result")
|
||||
val idServerUnbindResult: String? = null
|
||||
)
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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 org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class DeleteThreePidTask : Task<DeleteThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultDeleteThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
private val eventBus: EventBus) : DeleteThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
executeRequest<DeleteThreePidResponse>(eventBus) {
|
||||
val body = DeleteThreePidBody(
|
||||
medium = params.threePid.toMedium(),
|
||||
address = params.threePid.value
|
||||
)
|
||||
apiCall = profileAPI.deleteThreePid(body)
|
||||
}
|
||||
|
||||
// We do not really care about the result for the moment
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class FinalizeAddThreePidBody(
|
||||
/**
|
||||
* Required. The client secret used in the session with the homeserver.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The session identifier given by the homeserver.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
val sid: String,
|
||||
|
||||
/**
|
||||
* Additional authentication information for the user-interactive authentication API.
|
||||
*/
|
||||
@Json(name = "auth")
|
||||
val auth: UserPasswordAuth?
|
||||
)
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.failure.toRegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid,
|
||||
val session: String?,
|
||||
val accountPassword: String?
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus) : FinalizeAddingThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
// Get the required pending data
|
||||
val pendingThreePids = monarchy.fetchAllMappedSync(
|
||||
{ it.where(PendingThreePidEntity::class.java) },
|
||||
{ pendingThreePidMapper.map(it) }
|
||||
)
|
||||
.firstOrNull { it.threePid == params.threePid }
|
||||
?: throw IllegalArgumentException("unknown threepid")
|
||||
|
||||
try {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
val body = FinalizeAddThreePidBody(
|
||||
clientSecret = pendingThreePids.clientSecret,
|
||||
sid = pendingThreePids.sid,
|
||||
auth = if (params.session != null && params.accountPassword != null) {
|
||||
UserPasswordAuth(
|
||||
session = params.session,
|
||||
user = userId,
|
||||
password = params.accountPassword
|
||||
)
|
||||
} else null
|
||||
)
|
||||
apiCall = profileAPI.finalizeAddThreePid(body)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
throw throwable.toRegistrationFlowResponse()
|
||||
?.let { Failure.RegistrationFlowError(it) }
|
||||
?: throwable
|
||||
}
|
||||
|
||||
// Delete the pending three pid
|
||||
monarchy.awaitTransaction { realm ->
|
||||
realm.where(PendingThreePidEntity::class.java)
|
||||
.equalTo(PendingThreePidEntityFields.EMAIL, params.threePid.value)
|
||||
.or()
|
||||
.equalTo(PendingThreePidEntityFields.MSISDN, params.threePid.value)
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
||||
internal data class PendingThreePid(
|
||||
val threePid: ThreePid,
|
||||
val clientSecret: String,
|
||||
val sendAttempt: Int,
|
||||
val sid: String
|
||||
)
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class PendingThreePidMapper @Inject constructor() {
|
||||
|
||||
fun map(entity: PendingThreePidEntity): PendingThreePid {
|
||||
return PendingThreePid(
|
||||
threePid = entity.email?.let { ThreePid.Email(it) }
|
||||
?: entity.msisdn?.let { ThreePid.Msisdn(it) }
|
||||
?: error("Invalid data"),
|
||||
clientSecret = entity.clientSecret,
|
||||
sendAttempt = entity.sendAttempt,
|
||||
sid = entity.sid
|
||||
)
|
||||
}
|
||||
|
||||
fun map(domain: PendingThreePid): PendingThreePidEntity {
|
||||
return PendingThreePidEntity(
|
||||
email = domain.threePid.takeIf { it is ThreePid.Email }?.value,
|
||||
msisdn = domain.threePid.takeIf { it is ThreePid.Msisdn }?.value,
|
||||
clientSecret = domain.clientSecret,
|
||||
sendAttempt = domain.sendAttempt,
|
||||
sid = domain.sid
|
||||
)
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
internal interface ProfileAPI {
|
||||
|
||||
/**
|
||||
* Get the combined profile information for this user.
|
||||
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
|
||||
@ -71,4 +70,22 @@ internal interface ProfileAPI {
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
|
||||
fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken")
|
||||
fun addEmail(@Body body: AddEmailBody): Call<AddThreePidResponse>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add")
|
||||
fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call<Unit>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete")
|
||||
fun deleteThreePid(@Body body: DeleteThreePidBody): Call<DeleteThreePidResponse>
|
||||
}
|
||||
|
@ -58,4 +58,13 @@ internal abstract class ProfileModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindSetAvatarUrlTask(task: DefaultSetAvatarUrlTask): SetAvatarUrlTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindAddThreePidTask(task: DefaultAddThreePidTask): AddThreePidTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFinalizeAddingThreePidTask(task: DefaultFinalizeAddingThreePidTask): FinalizeAddingThreePidTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindDeleteThreePidTask(task: DefaultDeleteThreePidTask): DeleteThreePidTask
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragmen
|
||||
import im.vector.app.features.settings.locale.LocalePickerFragment
|
||||
import im.vector.app.features.settings.push.PushGatewaysFragment
|
||||
import im.vector.app.features.settings.push.PushRulesFragment
|
||||
import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
|
||||
import im.vector.app.features.share.IncomingShareFragment
|
||||
import im.vector.app.features.signout.soft.SoftLogoutFragment
|
||||
import im.vector.app.features.terms.ReviewTermsFragment
|
||||
@ -313,6 +314,11 @@ interface FragmentModule {
|
||||
@FragmentKey(VectorSettingsDevicesFragment::class)
|
||||
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(ThreePidsSettingsFragment::class)
|
||||
fun bindThreePidsSettingsFragment(fragment: ThreePidsSettingsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(PublicRoomsFragment::class)
|
||||
|
@ -59,35 +59,39 @@ class DefaultErrorFormatter @Inject constructor(
|
||||
}
|
||||
is Failure.ServerError -> {
|
||||
when {
|
||||
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
|
||||
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
|
||||
// Special case for terms and conditions
|
||||
stringProvider.getString(R.string.error_terms_not_accepted)
|
||||
}
|
||||
throwable.isInvalidPassword() -> {
|
||||
throwable.isInvalidPassword() -> {
|
||||
stringProvider.getString(R.string.auth_invalid_login_param)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_USER_IN_USE -> {
|
||||
throwable.error.code == MatrixError.M_USER_IN_USE -> {
|
||||
stringProvider.getString(R.string.login_signup_error_user_in_use)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_BAD_JSON -> {
|
||||
throwable.error.code == MatrixError.M_BAD_JSON -> {
|
||||
stringProvider.getString(R.string.login_error_bad_json)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_NOT_JSON -> {
|
||||
throwable.error.code == MatrixError.M_NOT_JSON -> {
|
||||
stringProvider.getString(R.string.login_error_not_json)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
|
||||
stringProvider.getString(R.string.login_error_threepid_denied)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
|
||||
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
|
||||
limitExceededError(throwable.error)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||
stringProvider.getString(R.string.login_reset_password_error_not_found)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
|
||||
stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
|
||||
}
|
||||
else -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_IN_USE
|
||||
&& throwable.error.message == "Email is already in use" -> {
|
||||
stringProvider.getString(R.string.account_email_already_used_error)
|
||||
}
|
||||
else -> {
|
||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||
@EpoxyAttribute
|
||||
var buttonAction: Action? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var destructiveButtonAction: Action? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickAction: Action? = null
|
||||
|
||||
@ -109,6 +112,11 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||
buttonAction?.perform?.run()
|
||||
}
|
||||
|
||||
holder.destructiveButton.setTextOrHide(destructiveButtonAction?.title)
|
||||
holder.destructiveButton.setOnClickListener {
|
||||
destructiveButtonAction?.perform?.run()
|
||||
}
|
||||
|
||||
holder.root.setOnClickListener {
|
||||
itemClickAction?.perform?.run()
|
||||
}
|
||||
@ -122,5 +130,6 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||
val accessoryImage by bind<ImageView>(R.id.item_generic_accessory_image)
|
||||
val progressBar by bind<ProgressBar>(R.id.item_generic_progress_bar)
|
||||
val actionButton by bind<Button>(R.id.item_generic_action_button)
|
||||
val destructiveButton by bind<Button>(R.id.item_generic_destructive_action_button)
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,11 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.util.Patterns
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.EditTextPreference
|
||||
@ -54,13 +52,11 @@ import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||
import im.vector.app.core.utils.TextUtils
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.copyToClipboard
|
||||
import im.vector.app.core.utils.getSizeOfFiles
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.media.createUCropWithDefaultSettings
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.SignOutUiWorker
|
||||
import im.vector.lib.multipicker.MultiPicker
|
||||
import im.vector.lib.multipicker.entity.MultiPickerImageType
|
||||
@ -187,44 +183,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
||||
mPasswordPreference.isVisible = false
|
||||
}
|
||||
|
||||
// Add Email
|
||||
findPreference<EditTextPreference>(ADD_EMAIL_PREFERENCE_KEY)!!.let {
|
||||
// It does not work on XML, do it here
|
||||
it.icon = activity?.let {
|
||||
ThemeUtils.tintDrawable(it,
|
||||
ContextCompat.getDrawable(it, R.drawable.ic_material_add)!!, R.attr.colorAccent)
|
||||
}
|
||||
|
||||
// Unfortunately, this is not supported in lib v7
|
||||
// it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
it.setOnPreferenceClickListener {
|
||||
notImplemented()
|
||||
true
|
||||
}
|
||||
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
notImplemented()
|
||||
// addEmail((newValue as String).trim())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Add phone number
|
||||
findPreference<VectorPreference>(ADD_PHONE_NUMBER_PREFERENCE_KEY)!!.let {
|
||||
// It does not work on XML, do it here
|
||||
it.icon = activity?.let {
|
||||
ThemeUtils.tintDrawable(it,
|
||||
ContextCompat.getDrawable(it, R.drawable.ic_material_add)!!, R.attr.colorAccent)
|
||||
}
|
||||
|
||||
it.setOnPreferenceClickListener {
|
||||
notImplemented()
|
||||
// TODO val intent = PhoneNumberAdditionActivity.getIntent(activity, session.credentials.userId)
|
||||
// startActivityForResult(intent, REQUEST_NEW_PHONE_NUMBER)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced settings
|
||||
|
||||
// user account
|
||||
@ -235,8 +193,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_HOME_SERVER_PREFERENCE_KEY)!!
|
||||
.summary = session.sessionParams.homeServerUrl
|
||||
|
||||
refreshEmailsList()
|
||||
refreshPhoneNumbersList()
|
||||
// Contacts
|
||||
setContactsPreferences()
|
||||
|
||||
@ -533,295 +489,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
||||
* Refresh phone number list
|
||||
*/
|
||||
private fun refreshPhoneNumbersList() {
|
||||
/* TODO
|
||||
val currentPhoneNumber3PID = ArrayList(session.myUser.getlinkedPhoneNumbers())
|
||||
|
||||
val phoneNumberList = ArrayList<String>()
|
||||
for (identifier in currentPhoneNumber3PID) {
|
||||
phoneNumberList.add(identifier.address)
|
||||
}
|
||||
|
||||
// check first if there is an update
|
||||
var isNewList = true
|
||||
if (phoneNumberList.size == mDisplayedPhoneNumber.size) {
|
||||
isNewList = !mDisplayedPhoneNumber.containsAll(phoneNumberList)
|
||||
}
|
||||
|
||||
if (isNewList) {
|
||||
// remove the displayed one
|
||||
run {
|
||||
var index = 0
|
||||
while (true) {
|
||||
val preference = mUserSettingsCategory.findPreference(PHONE_NUMBER_PREFERENCE_KEY_BASE + index)
|
||||
|
||||
if (null != preference) {
|
||||
mUserSettingsCategory.removePreference(preference)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// add new phone number list
|
||||
mDisplayedPhoneNumber = phoneNumberList
|
||||
|
||||
val addPhoneBtn = mUserSettingsCategory.findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY)
|
||||
?: return
|
||||
|
||||
var order = addPhoneBtn.order
|
||||
|
||||
for ((index, phoneNumber3PID) in currentPhoneNumber3PID.withIndex()) {
|
||||
val preference = VectorPreference(activity!!)
|
||||
|
||||
preference.title = getString(R.string.settings_phone_number)
|
||||
var phoneNumberFormatted = phoneNumber3PID.address
|
||||
try {
|
||||
// Attempt to format phone number
|
||||
val phoneNumber = PhoneNumberUtil.getInstance().parse("+$phoneNumberFormatted", null)
|
||||
phoneNumberFormatted = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
|
||||
} catch (e: NumberParseException) {
|
||||
// Do nothing, we will display raw version
|
||||
}
|
||||
|
||||
preference.summary = phoneNumberFormatted
|
||||
preference.key = PHONE_NUMBER_PREFERENCE_KEY_BASE + index
|
||||
preference.order = order
|
||||
|
||||
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
displayDelete3PIDConfirmationDialog(phoneNumber3PID, preference.summary)
|
||||
true
|
||||
}
|
||||
|
||||
preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
|
||||
override fun onPreferenceLongClick(preference: Preference): Boolean {
|
||||
activity?.let { copyToClipboard(it, phoneNumber3PID.address) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
order++
|
||||
mUserSettingsCategory.addPreference(preference)
|
||||
}
|
||||
|
||||
addPhoneBtn.order = order
|
||||
} */
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
// Email management
|
||||
// ==============================================================================================================
|
||||
|
||||
/**
|
||||
* Refresh the emails list
|
||||
*/
|
||||
private fun refreshEmailsList() {
|
||||
val currentEmail3PID = emptyList<String>() // TODO ArrayList(session.myUser.getlinkedEmails())
|
||||
|
||||
val newEmailsList = ArrayList<String>()
|
||||
for (identifier in currentEmail3PID) {
|
||||
// TODO newEmailsList.add(identifier.address)
|
||||
}
|
||||
|
||||
// check first if there is an update
|
||||
var isNewList = true
|
||||
if (newEmailsList.size == mDisplayedEmails.size) {
|
||||
isNewList = !mDisplayedEmails.containsAll(newEmailsList)
|
||||
}
|
||||
|
||||
if (isNewList) {
|
||||
// remove the displayed one
|
||||
run {
|
||||
var index = 0
|
||||
while (true) {
|
||||
val preference = mUserSettingsCategory.findPreference<VectorPreference>(EMAIL_PREFERENCE_KEY_BASE + index)
|
||||
|
||||
if (null != preference) {
|
||||
mUserSettingsCategory.removePreference(preference)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// add new emails list
|
||||
mDisplayedEmails = newEmailsList
|
||||
|
||||
val addEmailBtn = mUserSettingsCategory.findPreference<VectorPreference>(ADD_EMAIL_PREFERENCE_KEY) ?: return
|
||||
|
||||
var order = addEmailBtn.order
|
||||
|
||||
for ((index, email3PID) in currentEmail3PID.withIndex()) {
|
||||
val preference = VectorPreference(requireActivity())
|
||||
|
||||
preference.title = getString(R.string.settings_email_address)
|
||||
preference.summary = "TODO" // email3PID.address
|
||||
preference.key = EMAIL_PREFERENCE_KEY_BASE + index
|
||||
preference.order = order
|
||||
|
||||
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { pref ->
|
||||
displayDelete3PIDConfirmationDialog(/* TODO email3PID, */ pref.summary)
|
||||
true
|
||||
}
|
||||
|
||||
preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
|
||||
override fun onPreferenceLongClick(preference: Preference): Boolean {
|
||||
activity?.let { copyToClipboard(it, "TODO") } // email3PID.address) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
mUserSettingsCategory.addPreference(preference)
|
||||
|
||||
order++
|
||||
}
|
||||
|
||||
addEmailBtn.order = order
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to add a new email to the account
|
||||
*
|
||||
* @param email the email to add.
|
||||
*/
|
||||
private fun addEmail(email: String) {
|
||||
// check first if the email syntax is valid
|
||||
// if email is null , then also its invalid email
|
||||
if (email.isBlank() || !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
|
||||
activity?.toast(R.string.auth_invalid_email)
|
||||
return
|
||||
}
|
||||
|
||||
// check first if the email syntax is valid
|
||||
if (mDisplayedEmails.indexOf(email) >= 0) {
|
||||
activity?.toast(R.string.auth_email_already_defined)
|
||||
return
|
||||
}
|
||||
|
||||
notImplemented()
|
||||
/* TODO
|
||||
val pid = ThreePid(email, ThreePid.MEDIUM_EMAIL)
|
||||
|
||||
displayLoadingView()
|
||||
|
||||
session.myUser.requestEmailValidationToken(pid, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
activity?.runOnUiThread { showEmailValidationDialog(pid) }
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
if (TextUtils.equals(MatrixError.THREEPID_IN_USE, e.errcode)) {
|
||||
onCommonDone(getString(R.string.account_email_already_used_error))
|
||||
} else {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an email validation dialog to warn the user tho valid his email link.
|
||||
*
|
||||
* @param pid the used pid.
|
||||
*/
|
||||
/* TODO
|
||||
private fun showEmailValidationDialog(pid: ThreePid) {
|
||||
activity?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.account_email_validation_title)
|
||||
.setMessage(R.string.account_email_validation_message)
|
||||
.setPositiveButton(R.string._continue) { _, _ ->
|
||||
session.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
it.runOnUiThread {
|
||||
hideLoadingView()
|
||||
refreshEmailsList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) {
|
||||
it.runOnUiThread {
|
||||
hideLoadingView()
|
||||
it.toast(R.string.account_email_validation_error)
|
||||
}
|
||||
} else {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
hideLoadingView()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Display a dialog which asks confirmation for the deletion of a 3pid
|
||||
*
|
||||
* @param pid the 3pid to delete
|
||||
* @param preferenceSummary the displayed 3pid
|
||||
*/
|
||||
private fun displayDelete3PIDConfirmationDialog(/* TODO pid: ThirdPartyIdentifier,*/ preferenceSummary: CharSequence) {
|
||||
val mediumFriendlyName = "TODO" // ThreePid.getMediumFriendlyName(pid.medium, activity).toLowerCase(VectorLocale.applicationLocale)
|
||||
val dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary)
|
||||
|
||||
activity?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.dialog_title_confirmation)
|
||||
.setMessage(dialogMessage)
|
||||
.setPositiveButton(R.string.remove) { _, _ ->
|
||||
notImplemented()
|
||||
/* TODO
|
||||
displayLoadingView()
|
||||
|
||||
session.myUser.delete3Pid(pid, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Void?) {
|
||||
when (pid.medium) {
|
||||
ThreePid.MEDIUM_EMAIL -> refreshEmailsList()
|
||||
ThreePid.MEDIUM_MSISDN -> refreshPhoneNumbersList()
|
||||
}
|
||||
onCommonDone(null)
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
onCommonDone(e.localizedMessage)
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -985,12 +652,6 @@ private fun showEmailValidationDialog(pid: ThreePid) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ADD_EMAIL_PREFERENCE_KEY = "ADD_EMAIL_PREFERENCE_KEY"
|
||||
private const val ADD_PHONE_NUMBER_PREFERENCE_KEY = "ADD_PHONE_NUMBER_PREFERENCE_KEY"
|
||||
|
||||
private const val EMAIL_PREFERENCE_KEY_BASE = "EMAIL_PREFERENCE_KEY_BASE"
|
||||
private const val PHONE_NUMBER_PREFERENCE_KEY_BASE = "PHONE_NUMBER_PREFERENCE_KEY_BASE"
|
||||
|
||||
private const val REQUEST_NEW_PHONE_NUMBER = 456
|
||||
private const val REQUEST_PHONEBOOK_COUNTRY = 789
|
||||
}
|
||||
|
@ -28,6 +28,11 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
@ -41,11 +46,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -309,7 +309,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
if (!isPasswordRequestFound) {
|
||||
// LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
|
||||
// LoginFlowTypes.PASSWORD not supported, and this is the only one Element supports so far...
|
||||
setState {
|
||||
copy(
|
||||
request = Fail(failure)
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
||||
sealed class ThreePidsSettingsAction : VectorViewModelAction {
|
||||
data class AddThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
|
||||
data class ContinueThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
|
||||
data class AccountPassword(val password: String) : ThreePidsSettingsAction()
|
||||
data class DeleteThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.GenericItem
|
||||
import im.vector.app.core.ui.list.genericButtonItem
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.ui.list.genericItem
|
||||
import im.vector.app.features.discovery.settingsSectionTitleItem
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreePidsSettingsController @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider
|
||||
) : TypedEpoxyController<ThreePidsSettingsViewState>() {
|
||||
|
||||
interface InteractionListener {
|
||||
fun addEmail()
|
||||
fun addMsisdn()
|
||||
fun continueThreePid(threePid: ThreePid)
|
||||
fun deleteThreePid(threePid: ThreePid)
|
||||
}
|
||||
|
||||
var interactionListener: InteractionListener? = null
|
||||
|
||||
override fun buildModels(data: ThreePidsSettingsViewState?) {
|
||||
if (data == null) return
|
||||
when (data.threePids) {
|
||||
is Loading -> {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
loadingText(stringProvider.getString(R.string.loading))
|
||||
}
|
||||
}
|
||||
is Fail -> {
|
||||
genericFooterItem {
|
||||
id("fail")
|
||||
text(data.threePids.error.localizedMessage)
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
val dataList = data.threePids.invoke()
|
||||
buildThreePids(dataList, data.pendingThreePids)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildThreePids(list: List<ThreePid>, pendingThreePids: Async<List<ThreePid>>) {
|
||||
val splited = list.groupBy { it is ThreePid.Email }
|
||||
val emails = splited[true].orEmpty()
|
||||
val msisdn = splited[false].orEmpty()
|
||||
|
||||
settingsSectionTitleItem {
|
||||
id("email")
|
||||
title(stringProvider.getString(R.string.settings_emails))
|
||||
}
|
||||
|
||||
emails.forEach { buildThreePid("email_", it) }
|
||||
|
||||
// Pending threePids
|
||||
pendingThreePids.invoke()
|
||||
?.filterIsInstance(ThreePid.Email::class.java)
|
||||
?.forEach { buildPendingThreePid("email_", it) }
|
||||
|
||||
genericButtonItem {
|
||||
id("addEmail")
|
||||
text(stringProvider.getString(R.string.settings_add_email_address))
|
||||
textColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
buttonClickAction(View.OnClickListener { interactionListener?.addEmail() })
|
||||
}
|
||||
|
||||
settingsSectionTitleItem {
|
||||
id("msisdn")
|
||||
title(stringProvider.getString(R.string.settings_phone_numbers))
|
||||
}
|
||||
|
||||
msisdn.forEach { buildThreePid("msisdn_", it) }
|
||||
|
||||
// Pending threePids
|
||||
pendingThreePids.invoke()
|
||||
?.filterIsInstance(ThreePid.Msisdn::class.java)
|
||||
?.forEach { buildPendingThreePid("msisdn_", it) }
|
||||
|
||||
genericButtonItem {
|
||||
id("addMsisdn")
|
||||
text(stringProvider.getString(R.string.settings_add_phone_number))
|
||||
textColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
buttonClickAction(View.OnClickListener { interactionListener?.addMsisdn() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildThreePid(idPrefix: String, threePid: ThreePid) {
|
||||
genericItem {
|
||||
id(idPrefix + threePid.value)
|
||||
title(threePid.value)
|
||||
destructiveButtonAction(
|
||||
GenericItem.Action(stringProvider.getString(R.string.remove))
|
||||
.apply {
|
||||
perform = Runnable { interactionListener?.deleteThreePid(threePid) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPendingThreePid(idPrefix: String, threePid: ThreePid) {
|
||||
genericItem {
|
||||
id(idPrefix + threePid.value)
|
||||
title(threePid.value)
|
||||
if (threePid is ThreePid.Email) {
|
||||
description(stringProvider.getString(R.string.account_email_validation_message))
|
||||
}
|
||||
buttonAction(
|
||||
GenericItem.Action(stringProvider.getString(R.string._continue))
|
||||
.apply {
|
||||
perform = Runnable { interactionListener?.continueThreePid(threePid) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.dialogs.PromptPasswordDialog
|
||||
import im.vector.app.core.dialogs.withColoredButton
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.isEmail
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.toast
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreePidsSettingsFragment @Inject constructor(
|
||||
private val viewModelFactory: ThreePidsSettingsViewModel.Factory,
|
||||
private val epoxyController: ThreePidsSettingsController
|
||||
) :
|
||||
VectorBaseFragment(),
|
||||
ThreePidsSettingsViewModel.Factory by viewModelFactory,
|
||||
ThreePidsSettingsController.InteractionListener {
|
||||
|
||||
private val viewModel: ThreePidsSettingsViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
recyclerView.configureWith(epoxyController)
|
||||
epoxyController.interactionListener = this
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable)
|
||||
ThreePidsSettingsViewEvents.RequestPassword -> askUserPassword()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun askUserPassword() {
|
||||
PromptPasswordDialog().show(requireActivity()) { password ->
|
||||
viewModel.handle(ThreePidsSettingsAction.AccountPassword(password))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
recyclerView.cleanup()
|
||||
epoxyController.interactionListener = null
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_emails_and_phone_numbers_title)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
if (state.isLoading) {
|
||||
showLoadingDialog()
|
||||
} else {
|
||||
dismissLoadingDialog()
|
||||
}
|
||||
epoxyController.setData(state)
|
||||
}
|
||||
|
||||
override fun addEmail() {
|
||||
val inflater = requireActivity().layoutInflater
|
||||
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
||||
|
||||
val input = layout.findViewById<EditText>(R.id.editText)
|
||||
input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_add_email_address)
|
||||
.setView(layout)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val email = input.text.toString()
|
||||
doAddEmail(email)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun doAddEmail(email: String) {
|
||||
// Check that email is valid
|
||||
if (!email.isEmail()) {
|
||||
requireActivity().toast(R.string.auth_invalid_email)
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.handle(ThreePidsSettingsAction.AddThreePid(ThreePid.Email(email)))
|
||||
}
|
||||
|
||||
override fun addMsisdn() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun continueThreePid(threePid: ThreePid) {
|
||||
viewModel.handle(ThreePidsSettingsAction.ContinueThreePid(threePid))
|
||||
}
|
||||
|
||||
override fun deleteThreePid(threePid: ThreePid) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setMessage(getString(R.string.settings_remove_three_pid_confirmation_content, threePid.value))
|
||||
.setPositiveButton(R.string.remove) { _, _ ->
|
||||
viewModel.handle(ThreePidsSettingsAction.DeleteThreePid(threePid))
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class ThreePidsSettingsViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : ThreePidsSettingsViewEvents()
|
||||
object RequestPassword : ThreePidsSettingsViewEvents()
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
|
||||
class ThreePidsSettingsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: ThreePidsSettingsViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<ThreePidsSettingsViewState, ThreePidsSettingsAction, ThreePidsSettingsViewEvents>(initialState) {
|
||||
|
||||
// UIA session
|
||||
private var pendingThreePid: ThreePid? = null
|
||||
private var pendingSession: String? = null
|
||||
|
||||
private val loadingCallback: MatrixCallback<Unit> = object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
isLoading(false)
|
||||
|
||||
if (failure is Failure.RegistrationFlowError) {
|
||||
var isPasswordRequestFound = false
|
||||
|
||||
// We only support LoginFlowTypes.PASSWORD
|
||||
// Check if we can provide the user password
|
||||
failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
|
||||
isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
|
||||
}
|
||||
|
||||
if (isPasswordRequestFound) {
|
||||
pendingSession = failure.registrationFlowResponse.session
|
||||
_viewEvents.post(ThreePidsSettingsViewEvents.RequestPassword)
|
||||
} else {
|
||||
// LoginFlowTypes.PASSWORD not supported, and this is the only one Element supports so far...
|
||||
_viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure))
|
||||
}
|
||||
} else {
|
||||
_viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
pendingThreePid = null
|
||||
pendingSession = null
|
||||
isLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLoading(isLoading: Boolean) {
|
||||
setState {
|
||||
copy(
|
||||
isLoading = isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: ThreePidsSettingsViewState): ThreePidsSettingsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<ThreePidsSettingsViewModel, ThreePidsSettingsViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: ThreePidsSettingsViewState): ThreePidsSettingsViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
}
|
||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeThreePids()
|
||||
observePendingThreePids()
|
||||
}
|
||||
|
||||
private fun observeThreePids() {
|
||||
session.rx()
|
||||
.liveThreePIds(true)
|
||||
.execute {
|
||||
copy(
|
||||
threePids = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observePendingThreePids() {
|
||||
session.rx()
|
||||
.livePendingThreePIds()
|
||||
.execute {
|
||||
copy(
|
||||
pendingThreePids = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: ThreePidsSettingsAction) {
|
||||
when (action) {
|
||||
is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action)
|
||||
is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action)
|
||||
is ThreePidsSettingsAction.AccountPassword -> handleAccountPassword(action)
|
||||
is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleAddThreePid(action: ThreePidsSettingsAction.AddThreePid) {
|
||||
isLoading(true)
|
||||
viewModelScope.launch {
|
||||
session.addThreePid(action.threePid, loadingCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleContinueThreePid(action: ThreePidsSettingsAction.ContinueThreePid) {
|
||||
isLoading(true)
|
||||
pendingThreePid = action.threePid
|
||||
viewModelScope.launch {
|
||||
session.finalizeAddingThreePid(action.threePid, null, null, loadingCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAccountPassword(action: ThreePidsSettingsAction.AccountPassword) {
|
||||
val safeSession = pendingSession ?: return Unit
|
||||
.also { _viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalStateException("No pending session"))) }
|
||||
val safeThreePid = pendingThreePid ?: return Unit
|
||||
.also { _viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalStateException("No pending threePid"))) }
|
||||
isLoading(true)
|
||||
viewModelScope.launch {
|
||||
session.finalizeAddingThreePid(safeThreePid, safeSession, action.password, loadingCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteThreePid(action: ThreePidsSettingsAction.DeleteThreePid) {
|
||||
isLoading(true)
|
||||
viewModelScope.launch {
|
||||
session.deleteThreePid(action.threePid, loadingCallback)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.app.features.settings.threepids
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
||||
data class ThreePidsSettingsViewState(
|
||||
val isLoading: Boolean = false,
|
||||
val threePids: Async<List<ThreePid>> = Uninitialized,
|
||||
val pendingThreePids: Async<List<ThreePid>> = Uninitialized
|
||||
) : MvRxState
|
@ -91,7 +91,7 @@
|
||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Set a maw width because the text can be long -->
|
||||
<!-- Set a max width because the text can be long -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/item_generic_action_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
@ -102,10 +102,26 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/item_generic_destructive_action_button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_description_text"
|
||||
tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/item_generic_destructive_action_button"
|
||||
style="@style/VectorButtonStyleDestructive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_action_button"
|
||||
tools:text="@string/delete"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -682,6 +682,9 @@
|
||||
<string name="settings_add_3pid_flow_not_supported">You can\'t do this from Element mobile</string>
|
||||
<string name="settings_add_3pid_authentication_needed">Authentication is required</string>
|
||||
|
||||
<string name="settings_emails">Email addresses</string>
|
||||
<string name="settings_phone_numbers">Phone numbers</string>
|
||||
<string name="settings_remove_three_pid_confirmation_content">Remove %s?</string>
|
||||
|
||||
<string name="settings_notification_advanced">Advanced Notification Settings</string>
|
||||
<string name="settings_notification_by_event">Notification importance by event</string>
|
||||
@ -927,6 +930,9 @@
|
||||
<string name="settings_unignore_user">Show all messages from %s?\n\nNote that this action will restart the app and it may take some time.</string>
|
||||
<string name="passwords_do_not_match">Passwords do not match</string>
|
||||
|
||||
<string name="settings_emails_and_phone_numbers_title">Emails and phone numbers</string>
|
||||
<string name="settings_emails_and_phone_numbers_summary">Manage emails and phone numbers linked to your Matrix account</string>
|
||||
|
||||
<string name="settings_delete_notification_targets_confirmation">Are you sure you want to remove this notification target?</string>
|
||||
|
||||
<string name="settings_delete_threepid_confirmation">Are you sure you want to remove the %1$s %2$s?</string>
|
||||
|
@ -22,33 +22,11 @@
|
||||
android:summary="@string/change_password_summary"
|
||||
android:title="@string/settings_password" />
|
||||
|
||||
<!-- Email will be added here -->
|
||||
|
||||
<!-- Note: inputType does not work, it is set also in code, as well as iconTint -->
|
||||
<im.vector.app.core.preference.VectorEditTextPreference
|
||||
android:icon="@drawable/ic_material_add"
|
||||
android:inputType="textEmailAddress"
|
||||
android:key="ADD_EMAIL_PREFERENCE_KEY"
|
||||
android:order="100"
|
||||
android:title="@string/settings_add_email_address"
|
||||
app:iconTint="@color/riotx_accent" />
|
||||
|
||||
<!-- Phone will be added here -->
|
||||
|
||||
<!-- Note: iconTint does not work, it is also done in code -->
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
android:icon="@drawable/ic_material_add"
|
||||
android:key="ADD_PHONE_NUMBER_PREFERENCE_KEY"
|
||||
android:order="200"
|
||||
android:title="@string/settings_add_phone_number"
|
||||
app:iconTint="@color/riotx_accent" />
|
||||
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
android:order="1000"
|
||||
android:persistent="false"
|
||||
android:summary="@string/settings_discovery_manage"
|
||||
android:title="@string/settings_discovery_category"
|
||||
app:fragment="im.vector.app.features.discovery.DiscoverySettingsFragment" />
|
||||
android:key="SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY"
|
||||
android:summary="@string/settings_emails_and_phone_numbers_summary"
|
||||
android:title="@string/settings_emails_and_phone_numbers_title"
|
||||
app:fragment="im.vector.app.features.settings.threepids.ThreePidsSettingsFragment" />
|
||||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user