Identity: progressing

This commit is contained in:
Benoit Marty 2020-05-07 02:28:22 +02:00
parent a75242c79d
commit 3aa6de7cf5
10 changed files with 209 additions and 270 deletions

View File

@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
fun syncSession(session: Session) {
val lock = CountDownLatch(1)
session.open()
GlobalScope.launch(Dispatchers.Main) { session.open() }
session.startSync(true)
val syncLiveData = runBlocking(Dispatchers.Main) {

View File

@ -55,6 +55,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecr
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -99,6 +100,7 @@ internal class DefaultSession @Inject constructor(
private val accountService: Lazy<AccountService>,
private val timelineEventDecryptor: TimelineEventDecryptor,
private val shieldTrustUpdater: ShieldTrustUpdater,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val defaultIdentityService: DefaultIdentityService)
: Session,
RoomService by roomService.get(),
@ -178,7 +180,10 @@ internal class DefaultSession @Inject constructor(
isOpen = false
eventBus.unregister(this)
shieldTrustUpdater.stop()
defaultIdentityService.stop()
GlobalScope.launch(coroutineDispatchers.main) {
// This has to be done on main thread
defaultIdentityService.stop()
}
}
override fun getSyncStateLive(): LiveData<SyncState> {

View File

@ -39,9 +39,8 @@ import im.vector.matrix.android.internal.session.identity.db.IdentityServiceStor
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIdentity
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.launchToCallback
@ -81,16 +80,16 @@ internal class DefaultIdentityService @Inject constructor(
accountDataDataSource
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
.observeNotNull(lifecycleOwner) {
val identityServerContent = it.getOrNull()?.content?.toModel<UserAccountDataIdentity>()
val identityServerContent = it.getOrNull()?.content?.toModel<IdentityServerContent>()
if (identityServerContent != null) {
notifyIdentityServerUrlChange(identityServerContent.content?.baseUrl)
notifyIdentityServerUrlChange(identityServerContent.baseUrl)
}
// TODO Handle the case where the account data is deleted?
}
}
private fun notifyIdentityServerUrlChange(baseUrl: String?) {
// This is maybe not a real change (local echo of account data we are just setting
// This is maybe not a real change (local echo of account data we are just setting)
if (identityServiceStore.get()?.identityServerUrl == baseUrl) {
Timber.d("Local echo of identity server url change")
} else {
@ -169,11 +168,16 @@ internal class DefaultIdentityService @Inject constructor(
private suspend fun updateAccountData(url: String?) {
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
identityContent = IdentityContent(baseUrl = url)
identityContent = IdentityServerContent(baseUrl = url)
))
}
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
if (threePids.isEmpty()) {
callback.onSuccess(emptyList())
return NoOpCancellable
}
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
lookUpInternal(true, threePids)
}

View File

@ -28,7 +28,7 @@ internal fun IdentityServerEntity.Companion.get(realm: Realm): IdentityServerEnt
return realm.where<IdentityServerEntity>().findFirst()
}
internal fun IdentityServerEntity.Companion.getOrCreate(realm: Realm): IdentityServerEntity {
private fun IdentityServerEntity.Companion.getOrCreate(realm: Realm): IdentityServerEntity {
return get(realm) ?: realm.createObject()
}

View File

@ -28,8 +28,8 @@ internal class RealmIdentityServerStore @Inject constructor(
) : IdentityServiceStore {
override fun get(): IdentityServerEntity? {
return Realm.getInstance(realmConfiguration).use {
IdentityServerEntity.get(it)
return Realm.getInstance(realmConfiguration).use { realm ->
IdentityServerEntity.get(realm)?.let { realm.copyFromRealm(it) }
}
}

View File

@ -20,12 +20,12 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserAccountDataIdentity(
internal data class UserAccountDataIdentityServer(
@Json(name = "type") override val type: String = TYPE_IDENTITY_SERVER,
@Json(name = "content") val content: IdentityContent? = null
@Json(name = "content") val content: IdentityServerContent? = null
) : UserAccountData()
@JsonClass(generateAdapter = true)
internal data class IdentityContent(
internal data class IdentityServerContent(
@Json(name = "base_url") val baseUrl: String? = null
)

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
@ -33,7 +33,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
}
data class IdentityParams(override val type: String = UserAccountData.TYPE_IDENTITY_SERVER,
private val identityContent: IdentityContent
private val identityContent: IdentityServerContent
) : Params {
override fun getData(): Any {

View File

@ -18,12 +18,14 @@ package im.vector.riotx.features.discovery
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.google.i18n.phonenumbers.PhoneNumberUtil
import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import timber.log.Timber
import javax.inject.Inject
class DiscoverySettingsController @Inject constructor(
@ -66,18 +68,18 @@ class DiscoverySettingsController @Inject constructor(
}
when (data.phoneNumbersList) {
is Loading -> {
is Incomplete -> {
settingsLoadingItem {
id("phoneLoading")
}
}
is Fail -> {
is Fail -> {
settingsInfoItem {
id("msisdnListError")
helperText(data.phoneNumbersList.error.message)
}
}
is Success -> {
is Success -> {
val phones = data.phoneNumbersList.invoke()
if (phones.isEmpty()) {
settingsInfoItem {
@ -86,103 +88,21 @@ class DiscoverySettingsController @Inject constructor(
}
} else {
phones.forEach { piState ->
val phoneNumber = PhoneNumberUtil.getInstance()
.parse("+${piState.value}", null)
val phoneNumber = try {
PhoneNumberUtil.getInstance().parse("+${piState.threePid.value}", null)
} catch (t: Throwable) {
Timber.e(t, "Unable to parse the phone number")
null
}
?.let {
PhoneNumberUtil.getInstance().format(it, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
}
settingsTextButtonItem {
id(piState.value)
id(piState.threePid.value)
title(phoneNumber)
colorProvider(colorProvider)
stringProvider(stringProvider)
when {
piState.isShared is Loading -> buttonIndeterminate(true)
piState.isShared is Fail -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonStyle(SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE)
buttonTitle(stringProvider.getString(R.string.global_retry))
infoMessage(piState.isShared.error.message)
buttonClickListener(View.OnClickListener {
listener?.onTapRetryToRetrieveBindings()
})
}
piState.isShared is Success -> when (piState.isShared()) {
PidInfo.SharedState.SHARED,
PidInfo.SharedState.NOT_SHARED -> {
checked(piState.isShared() == PidInfo.SharedState.SHARED)
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
switchChangeListener { _, checked ->
if (checked) {
listener?.onTapShareMsisdn(piState.value)
} else {
listener?.onTapRevokeMsisdn(piState.value)
}
}
}
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonTitle("")
}
}
}
}
when (piState.isShared()) {
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
settingsItemText {
id("tverif" + piState.value)
descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
interactionListener(object : SettingsItemText.Listener {
override fun onValidate(code: String) {
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
listener?.checkMsisdnVerification(piState.value, code, bind)
}
})
}
}
else -> {
}
}
}
}
}
}
}
private fun buildMailSection(data: DiscoverySettingsState) {
settingsSectionTitle {
id("emails")
titleResId(R.string.settings_discovery_emails_title)
}
when (data.emailList) {
is Loading -> {
settingsLoadingItem {
id("mailLoading")
}
}
is Fail -> {
settingsInfoItem {
id("mailListError")
helperText(data.emailList.error.message)
}
}
is Success -> {
val emails = data.emailList.invoke()
if (emails.isEmpty()) {
settingsInfoItem {
id("no_emails")
helperText(stringProvider.getString(R.string.settings_discovery_no_mails))
}
} else {
emails.forEach { piState ->
settingsTextButtonItem {
id(piState.value)
title(piState.value)
colorProvider(colorProvider)
stringProvider(stringProvider)
when (piState.isShared) {
is Loading -> buttonIndeterminate(true)
is Fail -> {
@ -201,9 +121,95 @@ class DiscoverySettingsController @Inject constructor(
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
switchChangeListener { _, checked ->
if (checked) {
listener?.onTapShareEmail(piState.value)
listener?.onTapShareMsisdn(piState.threePid.value)
} else {
listener?.onTapRevokeEmail(piState.value)
listener?.onTapRevokeMsisdn(piState.threePid.value)
}
}
}
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonTitle("")
}
}
}
}
when (piState.isShared()) {
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
settingsItemText {
id("tverif" + piState.threePid.value)
descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
interactionListener(object : SettingsItemText.Listener {
override fun onValidate(code: String) {
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
listener?.checkMsisdnVerification(piState.threePid.value, code, bind)
}
})
}
}
else -> {
}
}
}
}
}
}
}
private fun buildMailSection(data: DiscoverySettingsState) {
settingsSectionTitle {
id("emails")
titleResId(R.string.settings_discovery_emails_title)
}
when (data.emailList) {
is Incomplete -> {
settingsLoadingItem {
id("mailLoading")
}
}
is Fail -> {
settingsInfoItem {
id("mailListError")
helperText(data.emailList.error.message)
}
}
is Success -> {
val emails = data.emailList.invoke()
if (emails.isEmpty()) {
settingsInfoItem {
id("no_emails")
helperText(stringProvider.getString(R.string.settings_discovery_no_mails))
}
} else {
emails.forEach { piState ->
settingsTextButtonItem {
id(piState.threePid.value)
title(piState.threePid.value)
colorProvider(colorProvider)
stringProvider(stringProvider)
when (piState.isShared) {
is Loading -> buttonIndeterminate(true)
is Fail -> {
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonStyle(SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE)
buttonTitle(stringProvider.getString(R.string.global_retry))
infoMessage(piState.isShared.error.message)
buttonClickListener(View.OnClickListener {
listener?.onTapRetryToRetrieveBindings()
})
}
is Success -> when (piState.isShared()) {
PidInfo.SharedState.SHARED,
PidInfo.SharedState.NOT_SHARED -> {
checked(piState.isShared() == PidInfo.SharedState.SHARED)
buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
switchChangeListener { _, checked ->
if (checked) {
listener?.onTapShareEmail(piState.threePid.value)
} else {
listener?.onTapRevokeEmail(piState.threePid.value)
}
}
}
@ -212,10 +218,10 @@ class DiscoverySettingsController @Inject constructor(
buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonTitleId(R.string._continue)
infoMessageTintColorId(R.color.vector_info_color)
infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.value))
infoMessage(stringProvider.getString(R.string.settings_discovery_confirm_mail, piState.threePid.value))
buttonClickListener(View.OnClickListener {
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
listener?.checkEmailVerification(piState.value, bind)
listener?.checkEmailVerification(piState.threePid.value, bind)
})
}
}

View File

@ -140,11 +140,7 @@ class DiscoverySettingsFragment @Inject constructor(
override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
//we should prompt if there are bound items with current is
val pidList = ArrayList<PidInfo>().apply {
state.emailList()?.let { addAll(it) }
state.phoneNumbersList()?.let { addAll(it) }
}
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
if (hasBoundIds) {
@ -164,11 +160,7 @@ class DiscoverySettingsFragment @Inject constructor(
override fun onTapDisconnectIdentityServer() {
//we should prompt if there are bound items with current is
withState(viewModel) { state ->
val pidList = ArrayList<PidInfo>().apply {
state.emailList()?.let { addAll(it) }
state.phoneNumbersList()?.let { addAll(it) }
}
val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
if (hasBoundIds) {

View File

@ -29,17 +29,21 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.identity.FoundThreePid
import im.vector.matrix.android.api.session.identity.IdentityServiceError
import im.vector.matrix.android.api.session.identity.IdentityServiceListener
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewEvents
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction
data class PidInfo(
val value: String,
val isShared: Async<SharedState>,
val _3pid: ThreePid? = null
// Retrieved from the homeserver
val threePid: ThreePid,
// Retrieved from IdentityServer, or transient state
val isShared: Async<SharedState>
) {
enum class SharedState {
SHARED,
@ -53,7 +57,7 @@ data class DiscoverySettingsState(
val identityServer: Async<String?> = Uninitialized,
val emailList: Async<List<PidInfo>> = Uninitialized,
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
// TODO Use ViewEvents
// TODO Use ViewEvents?
val termsNotSigned: Boolean = false
) : MvRxState
@ -100,13 +104,25 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
setState {
copy(identityServer = Success(identityServerUrl))
}
if (currentIS != identityServerUrl) refreshModel()
if (currentIS != identityServerUrl) retrieveBinding()
}
}
init {
setState {
copy(identityServer = Success(identityService.getCurrentIdentityServer()))
}
startListenToIdentityManager()
refreshModel()
observeThreePids()
}
private fun observeThreePids() {
session.rx()
.liveThreePIds()
.subscribe {
retrieveBinding(it)
}
.disposeOnClear()
}
override fun onCleared() {
@ -140,7 +156,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityServer = Success(action.url)
)
}
refreshModel()
retrieveBinding()
}
override fun onFailure(failure: Throwable) {
@ -162,12 +178,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun shareEmail(email: String) = withState { state ->
if (state.identityServer() == null) return@withState
changeMailState(email, Loading(), null)
changeMailState(email, Loading())
identityService.startBindSession(ThreePid.Email(email), null,
object : MatrixCallback<ThreePid> {
override fun onSuccess(data: ThreePid) {
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND), data)
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND)/* TODO , data*/)
}
override fun onFailure(failure: Throwable) {
@ -178,30 +194,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
})
}
private fun changeMailState(address: String, state: Async<PidInfo.SharedState>, threePid: ThreePid?) {
setState {
val currentMails = emailList() ?: emptyList()
copy(emailList = Success(
currentMails.map {
if (it.value == address) {
it.copy(
_3pid = threePid,
isShared = state
)
} else {
it
}
}
))
}
}
private fun changeMailState(address: String, state: Async<PidInfo.SharedState>) {
setState {
val currentMails = emailList() ?: emptyList()
copy(emailList = Success(
currentMails.map {
if (it.value == address) {
if (it.threePid.value == address) {
it.copy(isShared = state)
} else {
it
@ -211,16 +209,13 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
}
private fun changeMsisdnState(address: String, state: Async<PidInfo.SharedState>, threePid: ThreePid?) {
private fun changeMsisdnState(address: String, state: Async<PidInfo.SharedState>) {
setState {
val phones = phoneNumbersList() ?: emptyList()
copy(phoneNumbersList = Success(
phones.map {
if (it.value == address) {
it.copy(
_3pid = threePid,
isShared = state
)
if (it.threePid.value == address) {
it.copy(isShared = state)
} else {
it
}
@ -245,7 +240,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
if (data.first) {
// requires mail validation
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND), data.second)
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND) /* TODO , data.second */)
} else {
changeMailState(email, Success(PidInfo.SharedState.NOT_SHARED))
}
@ -271,7 +266,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityService.startUnBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<Pair<Boolean, ThreePid?>> {
override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
if (data.first /*requires mail validation */) {
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND), data.second)
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND) /* TODO , data.second */)
} else {
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_SHARED))
}
@ -297,7 +292,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityService.startBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<ThreePid> {
override fun onSuccess(data: ThreePid) {
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND), data)
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND) /* TODO , data */)
}
override fun onFailure(failure: Throwable) {
@ -308,21 +303,6 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
})
}
private fun changeMsisdnState(msisdn: String, sharedState: Async<PidInfo.SharedState>) {
setState {
val currentMsisdns = phoneNumbersList()!!
copy(phoneNumbersList = Success(
currentMsisdns.map {
if (it.value == msisdn) {
it.copy(isShared = sharedState)
} else {
it
}
})
)
}
}
private fun startListenToIdentityManager() {
identityService.addListener(identityServerManagerListener)
}
@ -331,115 +311,66 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityService.addListener(identityServerManagerListener)
}
private fun refreshModel() = withState { state ->
private fun retrieveBinding() {
retrieveBinding(session.getThreePids())
}
private fun retrieveBinding(threePids: List<ThreePid>) = withState { state ->
if (state.identityServer().isNullOrBlank()) return@withState
val emails = threePids.filterIsInstance<ThreePid.Email>()
val msisdns = threePids.filterIsInstance<ThreePid.Msisdn>()
setState {
copy(
emailList = Loading(),
phoneNumbersList = Loading()
emailList = Success(emails.map { PidInfo(it, Loading()) }),
phoneNumbersList = Success(msisdns.map { PidInfo(it, Loading()) })
)
}
/* TODO
session.refreshThirdPartyIdentifiers(object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
_errorLiveEvent.postValue(LiveEvent(failure))
identityService.lookUp(threePids,
object : MatrixCallback<List<FoundThreePid>> {
override fun onSuccess(data: List<FoundThreePid>) {
setState {
copy(
emailList = Success(toPidInfoList(emails, data)),
phoneNumbersList = Success(toPidInfoList(msisdns, data))
)
}
}
setState {
copy(
emailList = Fail(failure),
phoneNumbersList = Fail(failure)
)
}
}
override fun onFailure(failure: Throwable) {
if (failure is IdentityServiceError.TermsNotSignedException) {
setState {
// TODO Use ViewEvent ?
copy(termsNotSigned = true)
}
}
override fun onSuccess(data: Unit) {
setState {
copy(termsNotSigned = false)
}
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
retrieveBinding()
}
})
*/
setState {
copy(
emailList = Success(emails.map { PidInfo(it, Fail(failure)) }),
phoneNumbersList = Success(msisdns.map { PidInfo(it, Fail(failure)) })
)
}
}
})
}
private fun retrieveBinding() {
/* TODO
val linkedMailsInfo = session.myUser.getlinkedEmails()
val knownEmails = linkedMailsInfo.map { it.address }
// Note: it will be a list of "email"
val knownEmailMedium = linkedMailsInfo.map { it.medium }
val linkedMsisdnsInfo = session.myUser.getlinkedPhoneNumbers()
val knownMsisdns = linkedMsisdnsInfo.map { it.address }
// Note: it will be a list of "msisdn"
val knownMsisdnMedium = linkedMsisdnsInfo.map { it.medium }
setState {
copy(
emailList = Success(knownEmails.map { PidInfo(it, Loading()) }),
phoneNumbersList = Success(knownMsisdns.map { PidInfo(it, Loading()) })
)
}
identityService.lookup(knownEmails + knownMsisdns,
knownEmailMedium + knownMsisdnMedium,
object : MatrixCallback<List<FoundThreePid>> {
override fun onSuccess(data: List<FoundThreePid>) {
setState {
copy(
emailList = Success(toPidInfoList(knownEmails, data.take(knownEmails.size))),
phoneNumbersList = Success(toPidInfoList(knownMsisdns, data.takeLast(knownMsisdns.size)))
)
}
}
override fun onUnexpectedError(e: Exception) {
if (e is TermsNotSignedException) {
setState {
// TODO Use ViewEvent
copy(termsNotSigned = true)
}
}
onError(e)
}
override fun onNetworkError(e: Exception) {
onError(e)
}
override fun onMatrixError(e: MatrixError) {
onError(Throwable(e.message))
}
private fun onError(e: Throwable) {
_errorLiveEvent.postValue(LiveEvent(e))
setState {
copy(
emailList = Success(knownEmails.map { PidInfo(it, Fail(e)) }),
phoneNumbersList = Success(knownMsisdns.map { PidInfo(it, Fail(e)) })
)
}
}
})
*/
}
private fun toPidInfoList(addressList: List<String>, matrixIds: List<String>): List<PidInfo> {
return addressList.map {
val hasMatrixId = matrixIds[addressList.indexOf(it)].isNotBlank()
private fun toPidInfoList(threePids: List<ThreePid>, foundThreePids: List<FoundThreePid>): List<PidInfo> {
return threePids.map { threePid ->
val hasMatrixId = foundThreePids.any { it.threePid == threePid }
PidInfo(
value = it,
threePid = threePid,
isShared = Success(PidInfo.SharedState.SHARED.takeIf { hasMatrixId } ?: PidInfo.SharedState.NOT_SHARED)
)
}
}
private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state ->
val pid = state.phoneNumbersList()?.find { it.value == action.msisdn }?._3pid ?: return@withState
val pid = state.phoneNumbersList()?.find { it.threePid.value == action.msisdn }?.threePid ?: return@withState
identityService.submitValidationToken(pid,
action.code,
@ -460,11 +391,11 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
val _3pid = when (action.threePid) {
is ThreePid.Email -> {
changeMailState(action.threePid.email, Loading())
state.emailList()?.find { it.value == action.threePid.email }?._3pid ?: return@withState
state.emailList()?.find { it.threePid.value == action.threePid.email }?.threePid ?: return@withState
}
is ThreePid.Msisdn -> {
changeMsisdnState(action.threePid.msisdn, Loading())
state.phoneNumbersList()?.find { it.value == action.threePid.msisdn }?._3pid ?: return@withState
state.phoneNumbersList()?.find { it.threePid.value == action.threePid.msisdn }?.threePid ?: return@withState
}
}
@ -472,8 +403,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
override fun onSuccess(data: Unit) {
val sharedState = Success(if (action.bind) PidInfo.SharedState.SHARED else PidInfo.SharedState.NOT_SHARED)
when (action.threePid) {
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState, null)
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState, null)
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState)
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState)
}
}
@ -494,8 +425,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun refreshPendingEmailBindings() = withState { state ->
state.emailList()?.forEach { info ->
when (info.isShared()) {
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(info.value), true))
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(info.value), false))
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, true))
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, false))
else -> Unit
}
}