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) { fun syncSession(session: Session) {
val lock = CountDownLatch(1) val lock = CountDownLatch(1)
session.open() GlobalScope.launch(Dispatchers.Main) { session.open() }
session.startSync(true) session.startSync(true)
val syncLiveData = runBlocking(Dispatchers.Main) { 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.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread 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.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -99,6 +100,7 @@ internal class DefaultSession @Inject constructor(
private val accountService: Lazy<AccountService>, private val accountService: Lazy<AccountService>,
private val timelineEventDecryptor: TimelineEventDecryptor, private val timelineEventDecryptor: TimelineEventDecryptor,
private val shieldTrustUpdater: ShieldTrustUpdater, private val shieldTrustUpdater: ShieldTrustUpdater,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val defaultIdentityService: DefaultIdentityService) private val defaultIdentityService: DefaultIdentityService)
: Session, : Session,
RoomService by roomService.get(), RoomService by roomService.get(),
@ -178,7 +180,10 @@ internal class DefaultSession @Inject constructor(
isOpen = false isOpen = false
eventBus.unregister(this) eventBus.unregister(this)
shieldTrustUpdater.stop() shieldTrustUpdater.stop()
defaultIdentityService.stop() GlobalScope.launch(coroutineDispatchers.main) {
// This has to be done on main thread
defaultIdentityService.stop()
}
} }
override fun getSyncStateLive(): LiveData<SyncState> { 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.AccountDataDataSource
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull 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.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.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.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.task.launchToCallback
@ -81,16 +80,16 @@ internal class DefaultIdentityService @Inject constructor(
accountDataDataSource accountDataDataSource
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER) .getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
.observeNotNull(lifecycleOwner) { .observeNotNull(lifecycleOwner) {
val identityServerContent = it.getOrNull()?.content?.toModel<UserAccountDataIdentity>() val identityServerContent = it.getOrNull()?.content?.toModel<IdentityServerContent>()
if (identityServerContent != null) { if (identityServerContent != null) {
notifyIdentityServerUrlChange(identityServerContent.content?.baseUrl) notifyIdentityServerUrlChange(identityServerContent.baseUrl)
} }
// TODO Handle the case where the account data is deleted? // TODO Handle the case where the account data is deleted?
} }
} }
private fun notifyIdentityServerUrlChange(baseUrl: String?) { 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) { if (identityServiceStore.get()?.identityServerUrl == baseUrl) {
Timber.d("Local echo of identity server url change") Timber.d("Local echo of identity server url change")
} else { } else {
@ -169,11 +168,16 @@ internal class DefaultIdentityService @Inject constructor(
private suspend fun updateAccountData(url: String?) { private suspend fun updateAccountData(url: String?) {
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams( updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
identityContent = IdentityContent(baseUrl = url) identityContent = IdentityServerContent(baseUrl = url)
)) ))
} }
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable { 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) { return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
lookUpInternal(true, threePids) lookUpInternal(true, threePids)
} }

View file

@ -28,7 +28,7 @@ internal fun IdentityServerEntity.Companion.get(realm: Realm): IdentityServerEnt
return realm.where<IdentityServerEntity>().findFirst() 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() return get(realm) ?: realm.createObject()
} }

View file

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

View file

@ -20,12 +20,12 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class UserAccountDataIdentity( internal data class UserAccountDataIdentityServer(
@Json(name = "type") override val type: String = TYPE_IDENTITY_SERVER, @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() ) : UserAccountData()
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class IdentityContent( internal data class IdentityServerContent(
@Json(name = "base_url") val baseUrl: String? = null @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.di.UserId
import im.vector.matrix.android.internal.network.executeRequest 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.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.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus 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, data class IdentityParams(override val type: String = UserAccountData.TYPE_IDENTITY_SERVER,
private val identityContent: IdentityContent private val identityContent: IdentityServerContent
) : Params { ) : Params {
override fun getData(): Any { override fun getData(): Any {

View file

@ -18,12 +18,14 @@ package im.vector.riotx.features.discovery
import android.view.View import android.view.View
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class DiscoverySettingsController @Inject constructor( class DiscoverySettingsController @Inject constructor(
@ -66,18 +68,18 @@ class DiscoverySettingsController @Inject constructor(
} }
when (data.phoneNumbersList) { when (data.phoneNumbersList) {
is Loading -> { is Incomplete -> {
settingsLoadingItem { settingsLoadingItem {
id("phoneLoading") id("phoneLoading")
} }
} }
is Fail -> { is Fail -> {
settingsInfoItem { settingsInfoItem {
id("msisdnListError") id("msisdnListError")
helperText(data.phoneNumbersList.error.message) helperText(data.phoneNumbersList.error.message)
} }
} }
is Success -> { is Success -> {
val phones = data.phoneNumbersList.invoke() val phones = data.phoneNumbersList.invoke()
if (phones.isEmpty()) { if (phones.isEmpty()) {
settingsInfoItem { settingsInfoItem {
@ -86,103 +88,21 @@ class DiscoverySettingsController @Inject constructor(
} }
} else { } else {
phones.forEach { piState -> phones.forEach { piState ->
val phoneNumber = PhoneNumberUtil.getInstance() val phoneNumber = try {
.parse("+${piState.value}", null) PhoneNumberUtil.getInstance().parse("+${piState.threePid.value}", null)
} catch (t: Throwable) {
Timber.e(t, "Unable to parse the phone number")
null
}
?.let { ?.let {
PhoneNumberUtil.getInstance().format(it, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) PhoneNumberUtil.getInstance().format(it, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
} }
settingsTextButtonItem { settingsTextButtonItem {
id(piState.value) id(piState.threePid.value)
title(phoneNumber) title(phoneNumber)
colorProvider(colorProvider) colorProvider(colorProvider)
stringProvider(stringProvider) 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) { when (piState.isShared) {
is Loading -> buttonIndeterminate(true) is Loading -> buttonIndeterminate(true)
is Fail -> { is Fail -> {
@ -201,9 +121,95 @@ class DiscoverySettingsController @Inject constructor(
buttonType(SettingsTextButtonItem.ButtonType.SWITCH) buttonType(SettingsTextButtonItem.ButtonType.SWITCH)
switchChangeListener { _, checked -> switchChangeListener { _, checked ->
if (checked) { if (checked) {
listener?.onTapShareEmail(piState.value) listener?.onTapShareMsisdn(piState.threePid.value)
} else { } 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) buttonType(SettingsTextButtonItem.ButtonType.NORMAL)
buttonTitleId(R.string._continue) buttonTitleId(R.string._continue)
infoMessageTintColorId(R.color.vector_info_color) 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 { buttonClickListener(View.OnClickListener {
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND 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 -> override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
//we should prompt if there are bound items with current is //we should prompt if there are bound items with current is
val pidList = ArrayList<PidInfo>().apply { val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
state.emailList()?.let { addAll(it) }
state.phoneNumbersList()?.let { addAll(it) }
}
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED } val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
if (hasBoundIds) { if (hasBoundIds) {
@ -164,11 +160,7 @@ class DiscoverySettingsFragment @Inject constructor(
override fun onTapDisconnectIdentityServer() { override fun onTapDisconnectIdentityServer() {
//we should prompt if there are bound items with current is //we should prompt if there are bound items with current is
withState(viewModel) { state -> withState(viewModel) { state ->
val pidList = ArrayList<PidInfo>().apply { val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
state.emailList()?.let { addAll(it) }
state.phoneNumbersList()?.let { addAll(it) }
}
val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED } val hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
if (hasBoundIds) { if (hasBoundIds) {

View file

@ -29,17 +29,21 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session 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.IdentityServiceListener
import im.vector.matrix.android.api.session.identity.ThreePid 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.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewEvents import im.vector.riotx.core.platform.VectorViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
data class PidInfo( data class PidInfo(
val value: String, // Retrieved from the homeserver
val isShared: Async<SharedState>, val threePid: ThreePid,
val _3pid: ThreePid? = null // Retrieved from IdentityServer, or transient state
val isShared: Async<SharedState>
) { ) {
enum class SharedState { enum class SharedState {
SHARED, SHARED,
@ -53,7 +57,7 @@ data class DiscoverySettingsState(
val identityServer: Async<String?> = Uninitialized, val identityServer: Async<String?> = Uninitialized,
val emailList: Async<List<PidInfo>> = Uninitialized, val emailList: Async<List<PidInfo>> = Uninitialized,
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized, val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
// TODO Use ViewEvents // TODO Use ViewEvents?
val termsNotSigned: Boolean = false val termsNotSigned: Boolean = false
) : MvRxState ) : MvRxState
@ -100,13 +104,25 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
setState { setState {
copy(identityServer = Success(identityServerUrl)) copy(identityServer = Success(identityServerUrl))
} }
if (currentIS != identityServerUrl) refreshModel() if (currentIS != identityServerUrl) retrieveBinding()
} }
} }
init { init {
setState {
copy(identityServer = Success(identityService.getCurrentIdentityServer()))
}
startListenToIdentityManager() startListenToIdentityManager()
refreshModel() observeThreePids()
}
private fun observeThreePids() {
session.rx()
.liveThreePIds()
.subscribe {
retrieveBinding(it)
}
.disposeOnClear()
} }
override fun onCleared() { override fun onCleared() {
@ -140,7 +156,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityServer = Success(action.url) identityServer = Success(action.url)
) )
} }
refreshModel() retrieveBinding()
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
@ -162,12 +178,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun shareEmail(email: String) = withState { state -> private fun shareEmail(email: String) = withState { state ->
if (state.identityServer() == null) return@withState if (state.identityServer() == null) return@withState
changeMailState(email, Loading(), null) changeMailState(email, Loading())
identityService.startBindSession(ThreePid.Email(email), null, identityService.startBindSession(ThreePid.Email(email), null,
object : MatrixCallback<ThreePid> { object : MatrixCallback<ThreePid> {
override fun onSuccess(data: 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) { 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>) { private fun changeMailState(address: String, state: Async<PidInfo.SharedState>) {
setState { setState {
val currentMails = emailList() ?: emptyList() val currentMails = emailList() ?: emptyList()
copy(emailList = Success( copy(emailList = Success(
currentMails.map { currentMails.map {
if (it.value == address) { if (it.threePid.value == address) {
it.copy(isShared = state) it.copy(isShared = state)
} else { } else {
it 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 { setState {
val phones = phoneNumbersList() ?: emptyList() val phones = phoneNumbersList() ?: emptyList()
copy(phoneNumbersList = Success( copy(phoneNumbersList = Success(
phones.map { phones.map {
if (it.value == address) { if (it.threePid.value == address) {
it.copy( it.copy(isShared = state)
_3pid = threePid,
isShared = state
)
} else { } else {
it it
} }
@ -245,7 +240,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
override fun onSuccess(data: Pair<Boolean, ThreePid?>) { override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
if (data.first) { if (data.first) {
// requires mail validation // 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 { } else {
changeMailState(email, Success(PidInfo.SharedState.NOT_SHARED)) 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?>> { identityService.startUnBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<Pair<Boolean, ThreePid?>> {
override fun onSuccess(data: Pair<Boolean, ThreePid?>) { override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
if (data.first /*requires mail validation */) { 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 { } else {
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_SHARED)) 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> { identityService.startBindSession(ThreePid.Msisdn(msisdn, countryCode), null, object : MatrixCallback<ThreePid> {
override fun onSuccess(data: 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) { 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() { private fun startListenToIdentityManager() {
identityService.addListener(identityServerManagerListener) identityService.addListener(identityServerManagerListener)
} }
@ -331,115 +311,66 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
identityService.addListener(identityServerManagerListener) 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 if (state.identityServer().isNullOrBlank()) return@withState
val emails = threePids.filterIsInstance<ThreePid.Email>()
val msisdns = threePids.filterIsInstance<ThreePid.Msisdn>()
setState { setState {
copy( copy(
emailList = Loading(), emailList = Success(emails.map { PidInfo(it, Loading()) }),
phoneNumbersList = Loading() phoneNumbersList = Success(msisdns.map { PidInfo(it, Loading()) })
) )
} }
/* TODO identityService.lookUp(threePids,
session.refreshThirdPartyIdentifiers(object : MatrixCallback<Unit> { object : MatrixCallback<List<FoundThreePid>> {
override fun onFailure(failure: Throwable) { override fun onSuccess(data: List<FoundThreePid>) {
_errorLiveEvent.postValue(LiveEvent(failure)) setState {
copy(
emailList = Success(toPidInfoList(emails, data)),
phoneNumbersList = Success(toPidInfoList(msisdns, data))
)
}
}
setState { override fun onFailure(failure: Throwable) {
copy( if (failure is IdentityServiceError.TermsNotSignedException) {
emailList = Fail(failure), setState {
phoneNumbersList = Fail(failure) // TODO Use ViewEvent ?
) copy(termsNotSigned = true)
} }
} }
override fun onSuccess(data: Unit) { _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
setState {
copy(termsNotSigned = false)
}
retrieveBinding() setState {
} copy(
}) emailList = Success(emails.map { PidInfo(it, Fail(failure)) }),
*/ phoneNumbersList = Success(msisdns.map { PidInfo(it, Fail(failure)) })
)
}
}
})
} }
private fun retrieveBinding() { private fun toPidInfoList(threePids: List<ThreePid>, foundThreePids: List<FoundThreePid>): List<PidInfo> {
/* TODO return threePids.map { threePid ->
val linkedMailsInfo = session.myUser.getlinkedEmails() val hasMatrixId = foundThreePids.any { it.threePid == threePid }
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()
PidInfo( PidInfo(
value = it, threePid = threePid,
isShared = Success(PidInfo.SharedState.SHARED.takeIf { hasMatrixId } ?: PidInfo.SharedState.NOT_SHARED) isShared = Success(PidInfo.SharedState.SHARED.takeIf { hasMatrixId } ?: PidInfo.SharedState.NOT_SHARED)
) )
} }
} }
private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state -> 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, identityService.submitValidationToken(pid,
action.code, action.code,
@ -460,11 +391,11 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
val _3pid = when (action.threePid) { val _3pid = when (action.threePid) {
is ThreePid.Email -> { is ThreePid.Email -> {
changeMailState(action.threePid.email, Loading()) 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 -> { is ThreePid.Msisdn -> {
changeMsisdnState(action.threePid.msisdn, Loading()) 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) { override fun onSuccess(data: Unit) {
val sharedState = Success(if (action.bind) PidInfo.SharedState.SHARED else PidInfo.SharedState.NOT_SHARED) val sharedState = Success(if (action.bind) PidInfo.SharedState.SHARED else PidInfo.SharedState.NOT_SHARED)
when (action.threePid) { when (action.threePid) {
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState, null) is ThreePid.Email -> changeMailState(action.threePid.email, sharedState)
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState, null) is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState)
} }
} }
@ -494,8 +425,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
private fun refreshPendingEmailBindings() = withState { state -> private fun refreshPendingEmailBindings() = withState { state ->
state.emailList()?.forEach { info -> state.emailList()?.forEach { info ->
when (info.isShared()) { when (info.isShared()) {
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(info.value), true)) PidInfo.SharedState.NOT_VERIFIED_FOR_BIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, true))
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(info.value), false)) PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid, false))
else -> Unit else -> Unit
} }
} }