Identity: import UI/UX From Riot and adapt to RiotX architecture
This commit is contained in:
parent
0199cf9a03
commit
784918350b
@ -27,7 +27,7 @@ interface IdentityService {
|
||||
/**
|
||||
* Return the default identity server of the homeserver (using Wellknown request)
|
||||
*/
|
||||
fun getDefaultIdentityServer(): String?
|
||||
fun getDefaultIdentityServer(callback: MatrixCallback<String?>): Cancelable
|
||||
|
||||
fun getCurrentIdentityServer(): String?
|
||||
|
||||
@ -35,9 +35,11 @@ interface IdentityService {
|
||||
|
||||
fun disconnect()
|
||||
|
||||
fun bindThreePid()
|
||||
fun startBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<ThreePid>)
|
||||
fun finalizeBindSessionFor3PID(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>)
|
||||
fun submitValidationToken(pid: ThreePid, code: String, matrixCallback: MatrixCallback<Unit>)
|
||||
|
||||
fun unbindThreePid()
|
||||
fun startUnBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<Pair<Boolean, ThreePid?>>)
|
||||
|
||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||
|
||||
|
@ -18,5 +18,5 @@ package im.vector.matrix.android.api.session.identity
|
||||
|
||||
sealed class ThreePid(open val value: String) {
|
||||
data class Email(val email: String) : ThreePid(email)
|
||||
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
|
||||
data class Msisdn(val msisdn: String, val countryCode: String? = null) : ThreePid(msisdn)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ 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.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
@ -78,7 +79,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
// Observe the account data change
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY)
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val identityServerContent = it.getOrNull()?.content?.toModel<UserAccountDataIdentity>()
|
||||
if (identityServerContent != null) {
|
||||
@ -104,8 +105,10 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
override fun getDefaultIdentityServer(): String? {
|
||||
TODO("Not yet implemented")
|
||||
override fun getDefaultIdentityServer(callback: MatrixCallback<String?>): Cancelable {
|
||||
// TODO Use Wellknown request
|
||||
callback.onSuccess("https://vector.im")
|
||||
return NoOpCancellable
|
||||
}
|
||||
|
||||
override fun getCurrentIdentityServer(): String? {
|
||||
@ -116,22 +119,49 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun startBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<ThreePid>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun finalizeBindSessionFor3PID(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun submitValidationToken(pid: ThreePid, code: String, matrixCallback: MatrixCallback<Unit>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun startUnBindSession(threePid: ThreePid, nothing: Nothing?, matrixCallback: MatrixCallback<Pair<Boolean, ThreePid?>>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setNewIdentityServer(url: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val urlCandidate = url?.let { param ->
|
||||
buildString {
|
||||
if (!param.startsWith("http")) {
|
||||
append("https://")
|
||||
}
|
||||
append(param)
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val current = getCurrentIdentityServer()
|
||||
when (url) {
|
||||
when (urlCandidate) {
|
||||
current ->
|
||||
// Nothing to do
|
||||
Timber.d("Same URL, nothing to do")
|
||||
null -> {
|
||||
// TODO
|
||||
// Disconnect previous one if any
|
||||
// TODO Disconnect previous one if any
|
||||
identityServiceStore.setUrl(null)
|
||||
updateAccountData(null)
|
||||
}
|
||||
else -> {
|
||||
// TODO: check first that it is a valid identity server
|
||||
updateAccountData(url)
|
||||
// Try to get a token
|
||||
getIdentityServerToken(urlCandidate)
|
||||
|
||||
updateAccountData(urlCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,14 +173,6 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
))
|
||||
}
|
||||
|
||||
override fun bindThreePid() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun unbindThreePid() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
lookUpInternal(true, threePids)
|
||||
@ -181,15 +203,19 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
|
||||
if (entity.token == null) {
|
||||
// Try to get a token
|
||||
val openIdToken = openIdTokenTask.execute(Unit)
|
||||
|
||||
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||
val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
|
||||
|
||||
identityServiceStore.setToken(token.token)
|
||||
getIdentityServerToken(url)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getIdentityServerToken(url: String) {
|
||||
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||
|
||||
val openIdToken = openIdTokenTask.execute(Unit)
|
||||
val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
|
||||
|
||||
identityServiceStore.setToken(token.token)
|
||||
}
|
||||
|
||||
override fun addListener(listener: IdentityServiceListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
@ -30,6 +30,6 @@ abstract class UserAccountData : AccountDataContent {
|
||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
const val TYPE_IDENTITY = "m.identity"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataIdentity(
|
||||
@Json(name = "type") override val type: String = TYPE_IDENTITY,
|
||||
@Json(name = "type") override val type: String = TYPE_IDENTITY_SERVER,
|
||||
@Json(name = "content") val content: IdentityContent? = null
|
||||
) : UserAccountData()
|
||||
|
||||
|
@ -32,7 +32,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
||||
fun getData(): Any
|
||||
}
|
||||
|
||||
data class IdentityParams(override val type: String = UserAccountData.TYPE_IDENTITY,
|
||||
data class IdentityParams(override val type: String = UserAccountData.TYPE_IDENTITY_SERVER,
|
||||
private val identityContent: IdentityContent
|
||||
) : Params {
|
||||
|
||||
|
@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentFactory
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import im.vector.riotx.features.discovery.DiscoverySettingsFragment
|
||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||
@ -41,6 +42,7 @@ import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeF
|
||||
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
|
||||
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
|
||||
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
|
||||
import im.vector.riotx.features.discovery.change.SetIdentityServerFragment
|
||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||
import im.vector.riotx.features.home.HomeDetailFragment
|
||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||
@ -474,4 +476,14 @@ interface FragmentModule {
|
||||
@IntoMap
|
||||
@FragmentKey(SharedSecuredStorageKeyFragment::class)
|
||||
fun bindSharedSecuredStorageKeyFragment(fragment: SharedSecuredStorageKeyFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(SetIdentityServerFragment::class)
|
||||
fun bindSetIdentityServerFragment(fragment: SetIdentityServerFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(DiscoverySettingsFragment::class)
|
||||
fun bindDiscoverySettingsFragment(fragment: DiscoverySettingsFragment): Fragment
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromK
|
||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotx.features.discovery.DiscoverySharedViewModel
|
||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||
@ -118,4 +119,9 @@ interface ViewModelModule {
|
||||
@IntoMap
|
||||
@ViewModelKey(RoomProfileSharedActionViewModel::class)
|
||||
fun bindRoomProfileSharedActionViewModel(viewModel: RoomProfileSharedActionViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(DiscoverySharedViewModel::class)
|
||||
fun bindDiscoverySharedViewModel(viewModel: DiscoverySharedViewModel): ViewModel
|
||||
}
|
||||
|
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
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 javax.inject.Inject
|
||||
|
||||
class DiscoverySettingsController @Inject constructor(
|
||||
private val colorProvider: ColorProvider,
|
||||
private val stringProvider: StringProvider
|
||||
) : TypedEpoxyController<DiscoverySettingsState>() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(data: DiscoverySettingsState) {
|
||||
when (data.identityServer) {
|
||||
is Loading -> {
|
||||
settingsLoadingItem {
|
||||
id("identityServerLoading")
|
||||
}
|
||||
}
|
||||
is Fail -> {
|
||||
settingsInfoItem {
|
||||
id("identityServerError")
|
||||
helperText(data.identityServer.error.message)
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
buildIdentityServerSection(data)
|
||||
|
||||
val hasIdentityServer = data.identityServer().isNullOrBlank().not()
|
||||
|
||||
if (hasIdentityServer) {
|
||||
buildMailSection(data)
|
||||
buildPhoneNumberSection(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPhoneNumberSection(data: DiscoverySettingsState) {
|
||||
settingsSectionTitle {
|
||||
id("msisdn")
|
||||
titleResId(R.string.settings_discovery_msisdn_title)
|
||||
}
|
||||
|
||||
when (data.phoneNumbersList) {
|
||||
is Loading -> {
|
||||
settingsLoadingItem {
|
||||
id("phoneLoading")
|
||||
}
|
||||
}
|
||||
is Fail -> {
|
||||
settingsInfoItem {
|
||||
id("msisdnListError")
|
||||
helperText(data.phoneNumbersList.error.message)
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
val phones = data.phoneNumbersList.invoke()
|
||||
if (phones.isEmpty()) {
|
||||
settingsInfoItem {
|
||||
id("no_msisdn")
|
||||
helperText(stringProvider.getString(R.string.settings_discovery_no_msisdn))
|
||||
}
|
||||
} else {
|
||||
phones.forEach { piState ->
|
||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
||||
.parse("+${piState.value}", null)
|
||||
?.let {
|
||||
PhoneNumberUtil.getInstance().format(it, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
|
||||
}
|
||||
|
||||
settingsTextButtonItem {
|
||||
id(piState.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 -> {
|
||||
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.value)
|
||||
} else {
|
||||
listener?.onTapRevokeEmail(piState.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
PidInfo.SharedState.NOT_VERIFIED_FOR_BIND,
|
||||
PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND -> {
|
||||
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))
|
||||
buttonClickListener(View.OnClickListener {
|
||||
val bind = piState.isShared() == PidInfo.SharedState.NOT_VERIFIED_FOR_BIND
|
||||
listener?.checkEmailVerification(piState.value, bind)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildIdentityServerSection(data: DiscoverySettingsState) {
|
||||
val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
|
||||
|
||||
settingsSectionTitle {
|
||||
id("idsTitle")
|
||||
titleResId(R.string.identity_server)
|
||||
}
|
||||
|
||||
settingsItem {
|
||||
id("idServer")
|
||||
description(identityServer)
|
||||
}
|
||||
|
||||
settingsInfoItem {
|
||||
id("idServerFooter")
|
||||
if (data.termsNotSigned) {
|
||||
helperText(stringProvider.getString(R.string.settings_agree_to_terms, identityServer))
|
||||
showCompoundDrawable(true)
|
||||
itemClickListener(View.OnClickListener { listener?.onSelectIdentityServer() })
|
||||
} else {
|
||||
showCompoundDrawable(false)
|
||||
if (data.identityServer() != null) {
|
||||
helperText(stringProvider.getString(R.string.settings_discovery_identity_server_info, identityServer))
|
||||
} else {
|
||||
helperTextResId(R.string.settings_discovery_identity_server_info_none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settingsButtonItem {
|
||||
id("change")
|
||||
colorProvider(colorProvider)
|
||||
if (data.identityServer() != null) {
|
||||
buttonTitleId(R.string.change_identity_server)
|
||||
} else {
|
||||
buttonTitleId(R.string.add_identity_server)
|
||||
}
|
||||
buttonStyle(SettingsTextButtonItem.ButtonStyle.POSITIVE)
|
||||
buttonClickListener(View.OnClickListener {
|
||||
listener?.onTapChangeIdentityServer()
|
||||
})
|
||||
}
|
||||
|
||||
if (data.identityServer() != null) {
|
||||
settingsInfoItem {
|
||||
id("removeInfo")
|
||||
helperTextResId(R.string.settings_discovery_disconnect_identity_server_info)
|
||||
}
|
||||
settingsButtonItem {
|
||||
id("remove")
|
||||
colorProvider(colorProvider)
|
||||
buttonTitleId(R.string.disconnect_identity_server)
|
||||
buttonStyle(SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE)
|
||||
buttonClickListener(View.OnClickListener {
|
||||
listener?.onTapDisconnectIdentityServer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onSelectIdentityServer()
|
||||
fun onTapRevokeEmail(email: String)
|
||||
fun onTapShareEmail(email: String)
|
||||
fun checkEmailVerification(email: String, bind: Boolean)
|
||||
fun checkMsisdnVerification(msisdn: String, code: String, bind: Boolean)
|
||||
fun onTapRevokeMsisdn(msisdn: String)
|
||||
fun onTapShareMsisdn(msisdn: String)
|
||||
fun onTapChangeIdentityServer()
|
||||
fun onTapDisconnectIdentityServer()
|
||||
fun onTapRetryToRetrieveBindings()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.discovery.change.SetIdentityServerFragment
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class DiscoverySettingsFragment @Inject constructor(
|
||||
private val controller: DiscoverySettingsController,
|
||||
val viewModelFactory: DiscoverySettingsViewModel.Factory
|
||||
) : VectorBaseFragment(), DiscoverySettingsController.Listener {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||
|
||||
private val viewModel by fragmentViewModel(DiscoverySettingsViewModel::class)
|
||||
|
||||
lateinit var sharedViewModel: DiscoverySharedViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
|
||||
|
||||
controller.listener = this
|
||||
recyclerView.configureWith(controller)
|
||||
|
||||
sharedViewModel.navigateEvent.observe(viewLifecycleOwner, Observer {
|
||||
if (it.peekContent().first == DiscoverySharedViewModel.NEW_IDENTITY_SERVER_SET_REQUEST) {
|
||||
viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(it.peekContent().second))
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DiscoverySettingsViewEvents.Failure -> {
|
||||
// TODO Snackbar.make(view, throwable.toString(), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
recyclerView.cleanup()
|
||||
controller.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
controller.setData(state)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_discovery_category)
|
||||
|
||||
//If some 3pids are pending, we can try to check if they have been verified here
|
||||
viewModel.handle(DiscoverySettingsAction.Refresh)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
/* TODO
|
||||
if (requestCode == TERMS_REQUEST_CODE) {
|
||||
if (Activity.RESULT_OK == resultCode) {
|
||||
viewModel.refreshModel()
|
||||
} else {
|
||||
//add some error?
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onSelectIdentityServer() = withState(viewModel) { state ->
|
||||
if (state.termsNotSigned) {
|
||||
/*
|
||||
TODO
|
||||
ReviewTermsActivity.intent(requireContext(),
|
||||
TermsManager.ServiceType.IdentityService,
|
||||
SetIdentityServerViewModel.sanitatizeBaseURL(state.identityServer() ?: ""),
|
||||
null).also {
|
||||
startActivityForResult(it, TERMS_REQUEST_CODE)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTapRevokeEmail(email: String) {
|
||||
viewModel.handle(DiscoverySettingsAction.RevokeThreePid(ThreePid.Email(email)))
|
||||
}
|
||||
|
||||
override fun onTapShareEmail(email: String) {
|
||||
viewModel.handle(DiscoverySettingsAction.ShareThreePid(ThreePid.Email(email)))
|
||||
}
|
||||
|
||||
override fun checkEmailVerification(email: String, bind: Boolean) {
|
||||
viewModel.handle(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Email(email), bind))
|
||||
}
|
||||
|
||||
override fun checkMsisdnVerification(msisdn: String, code: String, bind: Boolean) {
|
||||
viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(msisdn, code, bind))
|
||||
}
|
||||
|
||||
override fun onTapRevokeMsisdn(msisdn: String) {
|
||||
viewModel.handle(DiscoverySettingsAction.RevokeThreePid(ThreePid.Msisdn(msisdn)))
|
||||
}
|
||||
|
||||
override fun onTapShareMsisdn(msisdn: String) {
|
||||
viewModel.handle(DiscoverySettingsAction.ShareThreePid(ThreePid.Msisdn(msisdn)))
|
||||
}
|
||||
|
||||
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 hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
|
||||
|
||||
if (hasBoundIds) {
|
||||
//we should prompt
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.change_identity_server)
|
||||
.setMessage(getString(R.string.settings_discovery_disconnect_with_bound_pid, state.identityServer(), state.identityServer()))
|
||||
.setPositiveButton(R.string._continue) { _, _ -> navigateToChangeIdentityServerFragment() }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
Unit
|
||||
} else {
|
||||
navigateToChangeIdentityServerFragment()
|
||||
}
|
||||
}
|
||||
|
||||
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 hasBoundIds = pidList.any { it.isShared() == PidInfo.SharedState.SHARED }
|
||||
|
||||
if (hasBoundIds) {
|
||||
//we should prompt
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.disconnect_identity_server)
|
||||
.setMessage(getString(R.string.settings_discovery_disconnect_with_bound_pid, state.identityServer(), state.identityServer()))
|
||||
.setPositiveButton(R.string._continue) { _, _ -> viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(null)) }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTapRetryToRetrieveBindings() {
|
||||
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
|
||||
}
|
||||
|
||||
private fun navigateToChangeIdentityServerFragment() {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
|
||||
.replace(R.id.vector_settings_page, SetIdentityServerFragment::class.java, null)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
}
|
@ -0,0 +1,503 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.identity.IdentityServiceListener
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
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
|
||||
) {
|
||||
enum class SharedState {
|
||||
SHARED,
|
||||
NOT_SHARED,
|
||||
NOT_VERIFIED_FOR_BIND,
|
||||
NOT_VERIFIED_FOR_UNBIND
|
||||
}
|
||||
}
|
||||
|
||||
data class DiscoverySettingsState(
|
||||
val identityServer: Async<String?> = Uninitialized,
|
||||
val emailList: Async<List<PidInfo>> = Uninitialized,
|
||||
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
|
||||
// TODO Use ViewEvents
|
||||
val termsNotSigned: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
sealed class DiscoverySettingsAction : VectorViewModelAction {
|
||||
object RetrieveBinding : DiscoverySettingsAction()
|
||||
object Refresh : DiscoverySettingsAction()
|
||||
|
||||
data class ChangeIdentityServer(val url: String?) : DiscoverySettingsAction()
|
||||
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||
data class FinalizeBind3pid(val threePid: ThreePid, val bind: Boolean) : DiscoverySettingsAction()
|
||||
data class SubmitMsisdnToken(val msisdn: String, val code: String, val bind: Boolean) : DiscoverySettingsAction()
|
||||
}
|
||||
|
||||
sealed class DiscoverySettingsViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : DiscoverySettingsViewEvents()
|
||||
}
|
||||
|
||||
class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: DiscoverySettingsState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<DiscoverySettingsState, DiscoverySettingsAction, DiscoverySettingsViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: DiscoverySettingsState): DiscoverySettingsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<DiscoverySettingsViewModel, DiscoverySettingsState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: DiscoverySettingsState): DiscoverySettingsViewModel? {
|
||||
val fragment: DiscoverySettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.viewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
private val identityService = session.identityService()
|
||||
|
||||
private val identityServerManagerListener = object : IdentityServiceListener {
|
||||
override fun onIdentityServerChange() = withState { state ->
|
||||
val identityServerUrl = identityService.getCurrentIdentityServer()
|
||||
val currentIS = state.identityServer()
|
||||
setState {
|
||||
copy(identityServer = Success(identityServerUrl))
|
||||
}
|
||||
if (currentIS != identityServerUrl) refreshModel()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
startListenToIdentityManager()
|
||||
refreshModel()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
stopListenToIdentityManager()
|
||||
}
|
||||
|
||||
override fun handle(action: DiscoverySettingsAction) {
|
||||
when (action) {
|
||||
DiscoverySettingsAction.Refresh -> refreshPendingEmailBindings()
|
||||
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
|
||||
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
|
||||
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
|
||||
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
|
||||
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action)
|
||||
is DiscoverySettingsAction.SubmitMsisdnToken -> submitMsisdnToken(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun changeIdentityServer(action: DiscoverySettingsAction.ChangeIdentityServer) {
|
||||
setState {
|
||||
copy(
|
||||
identityServer = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
session.identityService().setNewIdentityServer(action.url, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
identityServer = Success(action.url)
|
||||
)
|
||||
}
|
||||
refreshModel()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
identityServer = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun shareThreePid(action: DiscoverySettingsAction.ShareThreePid) {
|
||||
when (action.threePid) {
|
||||
is ThreePid.Email -> shareEmail(action.threePid.email)
|
||||
is ThreePid.Msisdn -> shareMsisdn(action.threePid.msisdn)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun shareEmail(email: String) = withState { state ->
|
||||
if (state.identityServer() == null) return@withState
|
||||
changeMailState(email, Loading(), null)
|
||||
|
||||
identityService.startBindSession(ThreePid.Email(email), null,
|
||||
object : MatrixCallback<ThreePid> {
|
||||
override fun onSuccess(data: ThreePid) {
|
||||
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_BIND), data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
|
||||
changeMailState(email, Fail(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
it.copy(isShared = state)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeMsisdnState(address: String, state: Async<PidInfo.SharedState>, threePid: ThreePid?) {
|
||||
setState {
|
||||
val phones = phoneNumbersList() ?: emptyList()
|
||||
copy(phoneNumbersList = Success(
|
||||
phones.map {
|
||||
if (it.value == address) {
|
||||
it.copy(
|
||||
_3pid = threePid,
|
||||
isShared = state
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private fun revokeThreePid(action: DiscoverySettingsAction.RevokeThreePid) {
|
||||
when (action.threePid) {
|
||||
is ThreePid.Email -> revokeEmail(action.threePid.email)
|
||||
is ThreePid.Msisdn -> revokeMsisdn(action.threePid.msisdn)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun revokeEmail(email: String) = withState { state ->
|
||||
if (state.identityServer() == null) return@withState
|
||||
if (state.emailList() == null) return@withState
|
||||
changeMailState(email, Loading())
|
||||
|
||||
identityService.startUnBindSession(ThreePid.Email(email), null, object : MatrixCallback<Pair<Boolean, ThreePid?>> {
|
||||
override fun onSuccess(data: Pair<Boolean, ThreePid?>) {
|
||||
if (data.first) {
|
||||
// requires mail validation
|
||||
changeMailState(email, Success(PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND), data.second)
|
||||
} else {
|
||||
changeMailState(email, Success(PidInfo.SharedState.NOT_SHARED))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
|
||||
changeMailState(email, Fail(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun revokeMsisdn(msisdn: String) = withState { state ->
|
||||
if (state.identityServer() == null) return@withState
|
||||
if (state.emailList() == null) return@withState
|
||||
changeMsisdnState(msisdn, Loading())
|
||||
|
||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
||||
.parse("+$msisdn", null)
|
||||
val countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
||||
|
||||
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)
|
||||
} else {
|
||||
changeMsisdnState(msisdn, Success(PidInfo.SharedState.NOT_SHARED))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
|
||||
changeMsisdnState(msisdn, Fail(failure))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun shareMsisdn(msisdn: String) = withState { state ->
|
||||
if (state.identityServer() == null) return@withState
|
||||
changeMsisdnState(msisdn, Loading())
|
||||
|
||||
val phoneNumber = PhoneNumberUtil.getInstance()
|
||||
.parse("+$msisdn", null)
|
||||
val countryCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
|
||||
changeMsisdnState(msisdn, Fail(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
private fun stopListenToIdentityManager() {
|
||||
identityService.addListener(identityServerManagerListener)
|
||||
}
|
||||
|
||||
private fun refreshModel() = withState { state ->
|
||||
if (state.identityServer().isNullOrBlank()) return@withState
|
||||
|
||||
setState {
|
||||
copy(
|
||||
emailList = Loading(),
|
||||
phoneNumbersList = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
/* TODO
|
||||
session.refreshThirdPartyIdentifiers(object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_errorLiveEvent.postValue(LiveEvent(failure))
|
||||
|
||||
setState {
|
||||
copy(
|
||||
emailList = Fail(failure),
|
||||
phoneNumbersList = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(termsNotSigned = false)
|
||||
}
|
||||
|
||||
retrieveBinding()
|
||||
}
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
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()
|
||||
PidInfo(
|
||||
value = it,
|
||||
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
|
||||
|
||||
identityService.submitValidationToken(pid,
|
||||
action.code,
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(ThreePid.Msisdn(action.msisdn), action.bind))
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
changeMsisdnState(action.msisdn, Fail(failure))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun finalizeBind3pid(action: DiscoverySettingsAction.FinalizeBind3pid) = withState { state ->
|
||||
val _3pid = when (action.threePid) {
|
||||
is ThreePid.Email -> {
|
||||
changeMailState(action.threePid.email, Loading())
|
||||
state.emailList()?.find { it.value == action.threePid.email }?._3pid ?: return@withState
|
||||
}
|
||||
is ThreePid.Msisdn -> {
|
||||
changeMsisdnState(action.threePid.msisdn, Loading())
|
||||
state.phoneNumbersList()?.find { it.value == action.threePid.msisdn }?._3pid ?: return@withState
|
||||
}
|
||||
}
|
||||
|
||||
identityService.finalizeBindSessionFor3PID(_3pid, object : MatrixCallback<Unit> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
|
||||
|
||||
// Restore previous state after an error
|
||||
val sharedState = Success(if (action.bind) PidInfo.SharedState.NOT_VERIFIED_FOR_BIND else PidInfo.SharedState.NOT_VERIFIED_FOR_UNBIND)
|
||||
when (action.threePid) {
|
||||
is ThreePid.Email -> changeMailState(action.threePid.email, sharedState)
|
||||
is ThreePid.Msisdn -> changeMsisdnState(action.threePid.msisdn, sharedState)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
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))
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO Rework this
|
||||
class DiscoverySharedViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
var navigateEvent = MutableLiveData<LiveEvent<Pair<String, String>>>()
|
||||
|
||||
companion object {
|
||||
const val NEW_IDENTITY_SERVER_SET_REQUEST = "NEW_IDENTITY_SERVER_SET_REQUEST"
|
||||
}
|
||||
|
||||
fun requestChangeToIdentityServer(server: String) {
|
||||
navigateEvent.postValue(LiveEvent(NEW_IDENTITY_SERVER_SET_REQUEST to server))
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_button)
|
||||
abstract class SettingsButtonItem : EpoxyModelWithHolder<SettingsButtonItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var colorProvider: ColorProvider
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonTitle: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var buttonTitleId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonStyle: SettingsTextButtonItem.ButtonStyle = SettingsTextButtonItem.ButtonStyle.POSITIVE
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
if (buttonTitleId != null) {
|
||||
holder.button.setText(buttonTitleId!!)
|
||||
} else {
|
||||
holder.button.setTextOrHide(buttonTitle)
|
||||
}
|
||||
|
||||
when (buttonStyle) {
|
||||
SettingsTextButtonItem.ButtonStyle.POSITIVE -> {
|
||||
holder.button.setTextColor(colorProvider.getColorFromAttribute(R.attr.colorAccent))
|
||||
}
|
||||
SettingsTextButtonItem.ButtonStyle.DESTRUCTIVE -> {
|
||||
holder.button.setTextColor(colorProvider.getColor(R.color.vector_error_color))
|
||||
}
|
||||
}
|
||||
|
||||
holder.button.setOnClickListener(buttonClickListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val button by bind<Button>(R.id.settings_item_button)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_radio_single_line)
|
||||
abstract class SettingsImageItem : EpoxyModelWithHolder<SettingsImageItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var titleResId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@DrawableRes
|
||||
var endIconResourceId: Int = -1
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
if (titleResId != null) {
|
||||
holder.textView.setText(titleResId!!)
|
||||
} else {
|
||||
holder.textView.setTextOrHide(title)
|
||||
}
|
||||
if (endIconResourceId != -1) {
|
||||
holder.accessoryImage.setImageResource(endIconResourceId)
|
||||
holder.accessoryImage.isVisible = true
|
||||
} else {
|
||||
holder.accessoryImage.isVisible = false
|
||||
}
|
||||
|
||||
holder.view.setOnClickListener(itemClickListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.settings_item_text)
|
||||
val accessoryImage by bind<ImageView>(R.id.settings_item_image)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_helper_info)
|
||||
abstract class SettingsInfoItem : EpoxyModelWithHolder<SettingsInfoItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var helperText: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var helperTextResId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@DrawableRes
|
||||
var compoundDrawable: Int = R.drawable.vector_warning_red
|
||||
|
||||
@EpoxyAttribute
|
||||
var showCompoundDrawable: Boolean = false
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
if (helperTextResId != null) {
|
||||
holder.text.setText(helperTextResId!!)
|
||||
} else {
|
||||
holder.text.setTextOrHide(helperText)
|
||||
}
|
||||
|
||||
holder.view.setOnClickListener(itemClickListener)
|
||||
|
||||
if (showCompoundDrawable) {
|
||||
holder.text.setCompoundDrawablesWithIntrinsicBounds(compoundDrawable, 0, 0, 0)
|
||||
} else {
|
||||
holder.text.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val text by bind<TextView>(R.id.settings_helper_text)
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_simple_item)
|
||||
abstract class SettingsItem : EpoxyModelWithHolder<SettingsItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var titleResId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var descriptionResId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var description: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
|
||||
if (titleResId != null) {
|
||||
holder.titleText.setText(titleResId!!)
|
||||
} else {
|
||||
holder.titleText.setTextOrHide(title)
|
||||
}
|
||||
|
||||
if (descriptionResId != null) {
|
||||
holder.descriptionText.setText(descriptionResId!!)
|
||||
} else {
|
||||
holder.descriptionText.setTextOrHide(description)
|
||||
}
|
||||
|
||||
//If there is only a description, use primary color
|
||||
// holder.descriptionText.setTextColor(
|
||||
// if (holder.titleText.text.isNullOrBlank()) {
|
||||
// ThemeUtils.getColor(holder.main.context, android.R.attr.textColorPrimary)
|
||||
// } else {
|
||||
// ThemeUtils.getColor(holder.main.context, android.R.attr.textColorSecondary)
|
||||
// }
|
||||
// )
|
||||
|
||||
holder.switchButton.isVisible = false
|
||||
|
||||
holder.view.setOnClickListener(itemClickListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val titleText by bind<TextView>(R.id.settings_item_title)
|
||||
val descriptionText by bind<TextView>(R.id.settings_item_description)
|
||||
val switchButton by bind<Switch>(R.id.settings_item_switch)
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_edit_text)
|
||||
abstract class SettingsItemText : EpoxyModelWithHolder<SettingsItemText.Holder>() {
|
||||
|
||||
@EpoxyAttribute var descriptionText: String? = null
|
||||
@EpoxyAttribute var errorText: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var interactionListener: Listener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.textView.setTextOrHide(descriptionText)
|
||||
|
||||
holder.validateButton.setOnClickListener {
|
||||
val code = holder.editText.text.toString()
|
||||
holder.editText.text.clear()
|
||||
interactionListener?.onValidate(code)
|
||||
}
|
||||
|
||||
if (errorText.isNullOrBlank()) {
|
||||
holder.textInputLayout.error = null
|
||||
} else {
|
||||
holder.textInputLayout.error = errorText
|
||||
}
|
||||
|
||||
holder.editText.setOnEditorActionListener { tv, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val code = tv.text.toString()
|
||||
interactionListener?.onValidate(code)
|
||||
holder.editText.text.clear()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.settings_item_description)
|
||||
val editText by bind<EditText>(R.id.settings_item_edittext)
|
||||
val textInputLayout by bind<TextInputLayout>(R.id.settings_item_enter_til)
|
||||
val validateButton by bind<Button>(R.id.settings_item_enter_button)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onValidate(code: String)
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_loading)
|
||||
abstract class SettingsLoadingItem : EpoxyModelWithHolder<SettingsLoadingItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var loadingText: String? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.textView.setTextOrHide(loadingText)
|
||||
holder.progressBar.isVisible = true
|
||||
holder.progressBar.animate()
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.loadingText)
|
||||
val progressBar by bind<ProgressBar>(R.id.loadingProgress)
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_section_title)
|
||||
abstract class SettingsSectionTitle : EpoxyModelWithHolder<SettingsSectionTitle.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var titleResId: Int? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
if (titleResId != null) {
|
||||
holder.textView.setText(titleResId!!)
|
||||
} else {
|
||||
holder.textView.setTextOrHide(title)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.settings_section_title_text)
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_settings_button_single_line)
|
||||
abstract class SettingsTextButtonItem : EpoxyModelWithHolder<SettingsTextButtonItem.Holder>() {
|
||||
|
||||
enum class ButtonStyle {
|
||||
POSITIVE,
|
||||
DESTRUCTIVE
|
||||
}
|
||||
|
||||
enum class ButtonType {
|
||||
NORMAL,
|
||||
SWITCH
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var colorProvider: ColorProvider
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var stringProvider: StringProvider
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var titleResId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonTitle: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var buttonTitleId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonStyle: ButtonStyle = ButtonStyle.POSITIVE
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonType: ButtonType = ButtonType.NORMAL
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonIndeterminate: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var checked: Boolean? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonClickListener: View.OnClickListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var switchChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var infoMessage: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@StringRes
|
||||
var infoMessageId: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@ColorRes
|
||||
var infoMessageTintColorId: Int = R.color.vector_error_color
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
if (titleResId != null) {
|
||||
holder.textView.setText(titleResId!!)
|
||||
} else {
|
||||
holder.textView.setTextOrHide(title, hideWhenBlank = false)
|
||||
}
|
||||
|
||||
if (buttonTitleId != null) {
|
||||
holder.button.setText(buttonTitleId!!)
|
||||
} else {
|
||||
holder.button.setTextOrHide(buttonTitle)
|
||||
}
|
||||
|
||||
if (buttonIndeterminate) {
|
||||
holder.spinner.isVisible = true
|
||||
holder.button.isInvisible = true
|
||||
holder.switchButton.isInvisible = true
|
||||
holder.switchButton.setOnCheckedChangeListener(null)
|
||||
holder.button.setOnClickListener(null)
|
||||
} else {
|
||||
holder.spinner.isVisible = false
|
||||
when (buttonType) {
|
||||
ButtonType.NORMAL -> {
|
||||
holder.button.isVisible = true
|
||||
holder.switchButton.isVisible = false
|
||||
when (buttonStyle) {
|
||||
ButtonStyle.POSITIVE -> {
|
||||
holder.button.setTextColor(colorProvider.getColorFromAttribute(R.attr.colorAccent))
|
||||
}
|
||||
ButtonStyle.DESTRUCTIVE -> {
|
||||
holder.button.setTextColor(colorProvider.getColor(R.color.vector_error_color))
|
||||
}
|
||||
}
|
||||
holder.button.setOnClickListener(buttonClickListener)
|
||||
}
|
||||
ButtonType.SWITCH -> {
|
||||
holder.button.isVisible = false
|
||||
holder.switchButton.isVisible = true
|
||||
//set to null before changing the state
|
||||
holder.switchButton.setOnCheckedChangeListener(null)
|
||||
checked?.let { holder.switchButton.isChecked = it }
|
||||
holder.switchButton.setOnCheckedChangeListener(switchChangeListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val errorMessage = infoMessageId?.let { stringProvider.getString(it) } ?: infoMessage
|
||||
if (errorMessage != null) {
|
||||
holder.errorTextView.isVisible = true
|
||||
holder.errorTextView.setTextOrHide(errorMessage)
|
||||
val errorColor = colorProvider.getColor(infoMessageTintColorId)
|
||||
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_notification_privacy_warning)?.apply {
|
||||
ThemeUtils.tintDrawableWithColor(this, errorColor)
|
||||
holder.textView.setCompoundDrawablesWithIntrinsicBounds(this, null, null, null)
|
||||
}
|
||||
holder.errorTextView.setTextColor(errorColor)
|
||||
} else {
|
||||
holder.errorTextView.isVisible = false
|
||||
holder.errorTextView.text = null
|
||||
holder.textView.setCompoundDrawables(null, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.settings_item_text)
|
||||
val button by bind<Button>(R.id.settings_item_button)
|
||||
val switchButton by bind<Switch>(R.id.settings_item_switch)
|
||||
val spinner by bind<ProgressBar>(R.id.settings_item_button_spinner)
|
||||
val errorTextView by bind<TextView>(R.id.settings_item_error_message)
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery.change
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ProgressBar
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.BindView
|
||||
import butterknife.OnTextChanged
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.discovery.DiscoverySharedViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetIdentityServerFragment @Inject constructor(
|
||||
val viewModelFactory: SetIdentityServerViewModel.Factory
|
||||
) : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_set_identity_server
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_phone_number_addition
|
||||
|
||||
@BindView(R.id.discovery_identity_server_enter_til)
|
||||
lateinit var mKeyInputLayout: TextInputLayout
|
||||
|
||||
@BindView(R.id.discovery_identity_server_enter_edittext)
|
||||
lateinit var mKeyTextEdit: EditText
|
||||
|
||||
@BindView(R.id.discovery_identity_server_loading)
|
||||
lateinit var mProgressBar: ProgressBar
|
||||
|
||||
private val viewModel by fragmentViewModel(SetIdentityServerViewModel::class)
|
||||
|
||||
lateinit var sharedViewModel: DiscoverySharedViewModel
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
if (state.isVerifyingServer) {
|
||||
mKeyTextEdit.isEnabled = false
|
||||
mProgressBar.isVisible = true
|
||||
} else {
|
||||
mKeyTextEdit.isEnabled = true
|
||||
mProgressBar.isVisible = false
|
||||
}
|
||||
val newText = state.newIdentityServer ?: ""
|
||||
if (!newText.equals(mKeyTextEdit.text.toString())) {
|
||||
mKeyTextEdit.setText(newText)
|
||||
}
|
||||
mKeyInputLayout.error = state.errorMessageId?.let { getString(it) }
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
// TODO Create another menu
|
||||
R.id.action_add_phone_number -> {
|
||||
withState(viewModel) { state ->
|
||||
if (!state.isVerifyingServer) {
|
||||
viewModel.handle(SetIdentityServerAction.DoChangeServerName)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
|
||||
|
||||
mKeyTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
withState(viewModel) { state ->
|
||||
if (!state.isVerifyingServer) {
|
||||
viewModel.handle(SetIdentityServerAction.DoChangeServerName)
|
||||
}
|
||||
}
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SetIdentityServerViewEvents.NoTerms -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_discovery_no_terms_title)
|
||||
.setMessage(R.string.settings_discovery_no_terms)
|
||||
.setPositiveButton(R.string._continue) { _, _ ->
|
||||
processIdentityServerChange()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
is SetIdentityServerViewEvents.TermsAccepted -> {
|
||||
processIdentityServerChange()
|
||||
}
|
||||
|
||||
is SetIdentityServerViewEvents.ShowTerms -> {
|
||||
/* TODO
|
||||
ReviewTermsActivity.intent(requireContext(),
|
||||
TermsManager.ServiceType.IdentityService,
|
||||
SetIdentityServerViewModel.sanitatizeBaseURL(event.newIdentityServer),
|
||||
null).also {
|
||||
startActivityForResult(it, TERMS_REQUEST_CODE)
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
/* TODO
|
||||
if (requestCode == TERMS_REQUEST_CODE) {
|
||||
if (Activity.RESULT_OK == resultCode) {
|
||||
processIdentityServerChange()
|
||||
} else {
|
||||
//add some error?
|
||||
}
|
||||
}
|
||||
*/
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private fun processIdentityServerChange() {
|
||||
withState(viewModel) { state ->
|
||||
if (state.newIdentityServer != null) {
|
||||
sharedViewModel.requestChangeToIdentityServer(state.newIdentityServer)
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnTextChanged(R.id.discovery_identity_server_enter_edittext)
|
||||
fun onTextEditChange(s: Editable?) {
|
||||
s?.toString()?.let { viewModel.handle(SetIdentityServerAction.UpdateServerName(it)) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.identity_server)
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.features.discovery.change
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.riotx.R
|
||||
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
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
|
||||
data class SetIdentityServerState(
|
||||
val existingIdentityServer: String? = null,
|
||||
val newIdentityServer: String? = null,
|
||||
@StringRes val errorMessageId: Int? = null,
|
||||
val isVerifyingServer: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
sealed class SetIdentityServerAction : VectorViewModelAction {
|
||||
data class UpdateServerName(val url: String) : SetIdentityServerAction()
|
||||
object DoChangeServerName : SetIdentityServerAction()
|
||||
}
|
||||
|
||||
sealed class SetIdentityServerViewEvents : VectorViewEvents {
|
||||
data class ShowTerms(val newIdentityServer: String) : SetIdentityServerViewEvents()
|
||||
object NoTerms : SetIdentityServerViewEvents()
|
||||
object TermsAccepted : SetIdentityServerViewEvents()
|
||||
}
|
||||
|
||||
class SetIdentityServerViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: SetIdentityServerState,
|
||||
private val mxSession: Session,
|
||||
stringProvider: StringProvider)
|
||||
: VectorViewModel<SetIdentityServerState, SetIdentityServerAction, SetIdentityServerViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: SetIdentityServerState): SetIdentityServerViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SetIdentityServerViewModel, SetIdentityServerState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: SetIdentityServerState): SetIdentityServerViewModel? {
|
||||
val fragment: SetIdentityServerFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.viewModelFactory.create(state)
|
||||
}
|
||||
|
||||
fun sanitatizeBaseURL(baseUrl: String): String {
|
||||
var baseUrl1 = baseUrl
|
||||
if (!baseUrl1.startsWith("http://") && !baseUrl1.startsWith("https://")) {
|
||||
baseUrl1 = "https://$baseUrl1"
|
||||
}
|
||||
return baseUrl1
|
||||
}
|
||||
}
|
||||
|
||||
val userLanguage = stringProvider.getString(R.string.resources_language)
|
||||
|
||||
override fun handle(action: SetIdentityServerAction) {
|
||||
when (action) {
|
||||
is SetIdentityServerAction.UpdateServerName -> updateServerName(action)
|
||||
SetIdentityServerAction.DoChangeServerName -> doChangeServerName()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun updateServerName(action: SetIdentityServerAction.UpdateServerName) {
|
||||
setState {
|
||||
copy(
|
||||
newIdentityServer = action.url,
|
||||
errorMessageId = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doChangeServerName() = withState {
|
||||
var baseUrl: String? = it.newIdentityServer
|
||||
if (baseUrl.isNullOrBlank()) {
|
||||
setState {
|
||||
copy(errorMessageId = R.string.settings_discovery_please_enter_server)
|
||||
}
|
||||
return@withState
|
||||
}
|
||||
// TODO baseUrl = sanitatizeBaseURL(baseUrl)
|
||||
setState {
|
||||
copy(isVerifyingServer = true)
|
||||
}
|
||||
|
||||
/* TODO
|
||||
mxSession.termsManager.get(TermsManager.ServiceType.IdentityService,
|
||||
baseUrl,
|
||||
object : ApiCallback<GetTermsResponse> {
|
||||
override fun onSuccess(info: GetTermsResponse) {
|
||||
//has all been accepted?
|
||||
setState {
|
||||
copy(isVerifyingServer = false)
|
||||
}
|
||||
val resp = info.serverResponse
|
||||
val tos = resp.getLocalizedTerms(userLanguage)
|
||||
if (tos.isEmpty()) {
|
||||
//prompt do not define policy
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.NoTerms)
|
||||
} else {
|
||||
val shouldPrompt = tos.any { !info.alreadyAcceptedTermUrls.contains(it.localizedUrl) }
|
||||
if (shouldPrompt) {
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.ShowTerms(baseUrl))
|
||||
} else {
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.TermsAccepted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
if (e is HttpException && e.httpError.httpCode == 404) {
|
||||
setState {
|
||||
copy(isVerifyingServer = false)
|
||||
}
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.NoTerms)
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
isVerifyingServer = false,
|
||||
errorMessageId = R.string.settings_discovery_bad_identity_server
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
setState {
|
||||
copy(
|
||||
isVerifyingServer = false,
|
||||
errorMessageId = R.string.settings_discovery_bad_identity_server
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
setState {
|
||||
copy(
|
||||
isVerifyingServer = false,
|
||||
errorMessageId = R.string.settings_discovery_bad_identity_server
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
@ -165,8 +165,9 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
||||
.summary = session.sessionParams.homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
// identity server
|
||||
// TODO Handle refresh of the value
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)!!
|
||||
.summary = session.sessionParams.homeServerConnectionConfig.identityServerUri.toString()
|
||||
.summary = session.identityService().getCurrentIdentityServer() ?: getString(R.string.identity_server_not_defined)
|
||||
|
||||
refreshEmailsList()
|
||||
refreshPhoneNumbersList()
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
52
vector/src/main/res/layout/fragment_set_identity_server.xml
Normal file
52
vector/src/main/res/layout/fragment_set_identity_server.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/discovery_identity_server_enter_til"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/discovery_identity_server_enter_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/settings_discovery_enter_identity_server"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textUri"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:text="vector.im" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/discovery_identity_server_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/discovery_identity_server_enter_til"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
15
vector/src/main/res/layout/item_settings_button.xml
Normal file
15
vector/src/main/res/layout/item_settings_button.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_item_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
tools:text="@string/action_change" />
|
||||
|
||||
</FrameLayout>
|
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="15sp"
|
||||
tools:drawableLeft="@drawable/ic_notification_privacy_warning"
|
||||
tools:drawableStart="@drawable/ic_notification_privacy_warning"
|
||||
tools:drawableTint="@color/vector_error_color"
|
||||
tools:text="foo@bar.test" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:minWidth="70dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_item_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
tools:text="@string/share"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/settings_item_button_spinner"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="invisible" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/settings_item_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_error_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
tools:drawableStart="@drawable/ic_notification_privacy_warning"
|
||||
tools:text="Error Message"
|
||||
tools:textColor="@color/vector_info_color"
|
||||
tools:visibility="visible">
|
||||
|
||||
</TextView>
|
||||
</LinearLayout>
|
56
vector/src/main/res/layout/item_settings_edit_text.xml
Normal file
56
vector/src/main/res/layout/item_settings_edit_text.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/settings_text_message_sent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/settings_item_enter_til"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/settings_item_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone|flagNoPersonalizedLearning"
|
||||
android:inputType="numberDecimal"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:text="1234" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_item_enter_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/_continue" />
|
||||
|
||||
</LinearLayout>
|
15
vector/src/main/res/layout/item_settings_helper_info.xml
Normal file
15
vector/src/main/res/layout/item_settings_helper_info.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/settings_helper_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
tools:drawableStart="@drawable/vector_warning_red"
|
||||
tools:text="If you don’t want this, opt out below. You can also manage any of these preferences in Settings." />
|
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/settings_item_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
tools:src="@drawable/unit_test" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="15sp"
|
||||
tools:text="An option" />
|
||||
|
||||
</LinearLayout>
|
15
vector/src/main/res/layout/item_settings_section_title.xml
Normal file
15
vector/src/main/res/layout/item_settings_section_title.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/settings_section_title_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Title" />
|
47
vector/src/main/res/layout/item_settings_simple_item.xml
Normal file
47
vector/src/main/res/layout/item_settings_simple_item.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:orientation="vertical"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_item_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:orientation="vertical"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
tools:text="Description / Value" />
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/settings_item_switch"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</LinearLayout>
|
11
vector/src/main/res/menu/menu_phone_number_addition.xml
Normal file
11
vector/src/main/res/menu/menu_phone_number_addition.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add_phone_number"
|
||||
android:icon="@drawable/ic_material_done_white"
|
||||
android:title="@string/settings_add_phone_number"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
@ -5,6 +5,7 @@
|
||||
<color name="vector_success_color">#70BF56</color>
|
||||
<color name="vector_warning_color">#ff4b55</color>
|
||||
<color name="vector_error_color">#ff4b55</color>
|
||||
<color name="vector_info_color">#2f9edb</color>
|
||||
|
||||
<!-- main app colors -->
|
||||
<color name="vector_fuchsia_color">#ff4b55</color>
|
||||
|
@ -43,6 +43,13 @@
|
||||
android:title="@string/settings_add_phone_number"
|
||||
app:iconTint="?attr/vctr_settings_icon_tint_color" />
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreference
|
||||
android:order="1000"
|
||||
android:persistent="false"
|
||||
android:summary="@string/settings_discovery_manage"
|
||||
android:title="@string/settings_discovery_category"
|
||||
app:fragment="im.vector.riotx.features.discovery.DiscoverySettingsFragment" />
|
||||
|
||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||
@ -76,7 +83,7 @@
|
||||
<im.vector.riotx.core.preference.VectorPreference
|
||||
android:key="SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
|
||||
android:title="@string/settings_identity_server"
|
||||
app:isPreferenceVisible="@bool/false_not_implemented"
|
||||
app:fragment="im.vector.riotx.features.discovery.DiscoverySettingsFragment"
|
||||
tools:summary="https://identity.server.url" />
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreference
|
||||
|
Loading…
x
Reference in New Issue
Block a user