From 3aa6de7cf5350e311940bf92f6d123fa3be8c10c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 7 May 2020 02:28:22 +0200 Subject: [PATCH] Identity: progressing --- .../matrix/android/common/CommonTestHelper.kt | 3 +- .../internal/session/DefaultSession.kt | 7 +- .../identity/DefaultIdentityService.kt | 16 +- .../identity/db/IdentityServerQuery.kt | 2 +- .../identity/db/RealmIdentityServerStore.kt | 4 +- ...ty.kt => UserAccountDataIdentityServer.kt} | 6 +- .../accountdata/UpdateUserAccountDataTask.kt | 4 +- .../discovery/DiscoverySettingsController.kt | 198 +++++++-------- .../discovery/DiscoverySettingsFragment.kt | 12 +- .../discovery/DiscoverySettingsViewModel.kt | 227 ++++++------------ 10 files changed, 209 insertions(+), 270 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/{UserAccountDataIdentity.kt => UserAccountDataIdentityServer.kt} (85%) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 5bc8653f3d..3ca04a86d1 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -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) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 1dfade2388..edebc2987d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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, 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 { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt index 5a4a66a722..2c3447caa3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt @@ -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() + val identityServerContent = it.getOrNull()?.content?.toModel() 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, callback: MatrixCallback>): Cancelable { + if (threePids.isEmpty()) { + callback.onSuccess(emptyList()) + return NoOpCancellable + } + return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { lookUpInternal(true, threePids) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityServerQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityServerQuery.kt index 7b7bec13c0..c726b35dd7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityServerQuery.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityServerQuery.kt @@ -28,7 +28,7 @@ internal fun IdentityServerEntity.Companion.get(realm: Realm): IdentityServerEnt return realm.where().findFirst() } -internal fun IdentityServerEntity.Companion.getOrCreate(realm: Realm): IdentityServerEntity { +private fun IdentityServerEntity.Companion.getOrCreate(realm: Realm): IdentityServerEntity { return get(realm) ?: realm.createObject() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityServerStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityServerStore.kt index 2c7ffbd75f..7fc212a9eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityServerStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityServerStore.kt @@ -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) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt similarity index 85% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentity.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt index 354022420e..4af2034d64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt @@ -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 ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 01dc297946..65babd6b2c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -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 { + 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) }) } } diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt index bdf0ca6de4..2a9cf5de0f 100644 --- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt @@ -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().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().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) { diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt index 34c5bcf66b..a0af865b3e 100644 --- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt @@ -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, - val _3pid: ThreePid? = null + // Retrieved from the homeserver + val threePid: ThreePid, + // Retrieved from IdentityServer, or transient state + val isShared: Async ) { enum class SharedState { SHARED, @@ -53,7 +57,7 @@ data class DiscoverySettingsState( val identityServer: Async = Uninitialized, val emailList: Async> = Uninitialized, val phoneNumbersList: Async> = 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 { 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, 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) { 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, threePid: ThreePid?) { + private fun changeMsisdnState(address: String, state: Async) { 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) { 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> { override fun onSuccess(data: Pair) { 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 { 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) { - 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) = withState { state -> if (state.identityServer().isNullOrBlank()) return@withState + val emails = threePids.filterIsInstance() + val msisdns = threePids.filterIsInstance() + 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 { - override fun onFailure(failure: Throwable) { - _errorLiveEvent.postValue(LiveEvent(failure)) + identityService.lookUp(threePids, + object : MatrixCallback> { + override fun onSuccess(data: List) { + 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> { - override fun onSuccess(data: List) { - 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, matrixIds: List): List { - return addressList.map { - val hasMatrixId = matrixIds[addressList.indexOf(it)].isNotBlank() + private fun toPidInfoList(threePids: List, foundThreePids: List): List { + 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 } }