mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge branch 'develop' into feature/some_upgrade
This commit is contained in:
commit
b7d86c3fa4
33
CHANGES.md
33
CHANGES.md
@ -1,4 +1,4 @@
|
||||
Changes in Riot.imX 0.91.4 (2020-XX-XX)
|
||||
Changes in Riot.imX 0.91.5 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
@ -8,11 +8,7 @@ Improvements 🙌:
|
||||
-
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when coming from a notification (#1601)
|
||||
- Fix Exception when importing keys (#1576)
|
||||
- File isn't downloaded when another file with the same name already exists (#1578)
|
||||
- saved images don't show up in gallery (#1324)
|
||||
- Fix reply fallback leaking sender locale (#429)
|
||||
-
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
@ -21,14 +17,33 @@ SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
- Fix lint false-positive about WorkManger (#1012)
|
||||
- Upgrade build-tools from 3.5.3 to 3.6.6
|
||||
- Upgrade gradle from 5.4.1 to 5.6.4
|
||||
- Upgrade some dependencies
|
||||
- Revert to build-tools 3.5.3
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Changes in Riot.imX 0.91.4 (2020-07-06)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Re-activate Wellknown support with updated UI (#1614)
|
||||
|
||||
Improvements 🙌:
|
||||
- Upload device keys only once to the homeserver and fix crash when no network (#1629)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when coming from a notification (#1601)
|
||||
- Fix Exception when importing keys (#1576)
|
||||
- File isn't downloaded when another file with the same name already exists (#1578)
|
||||
- saved images don't show up in gallery (#1324)
|
||||
- Fix reply fallback leaking sender locale (#429)
|
||||
|
||||
Build 🧱:
|
||||
- Fix lint false-positive about WorkManager (#1012)
|
||||
- Upgrade build-tools from 3.5.3 to 3.6.3
|
||||
- Upgrade gradle from 5.4.1 to 5.6.4
|
||||
|
||||
Changes in Riot.imX 0.91.3 (2020-07-01)
|
||||
===================================================
|
||||
|
||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.auth.login
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||
@ -27,6 +28,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||
import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
@ -49,13 +51,28 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
|
||||
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||
val client = buildClient(params.homeServerConnectionConfig)
|
||||
val authAPI = retrofitFactory.create(client, params.homeServerConnectionConfig.homeServerUri.toString())
|
||||
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
val authAPI = retrofitFactory.create(client, homeServerUrl)
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
val credentials = try {
|
||||
executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is UnrecognizedCertificateException -> {
|
||||
throw Failure.UnrecognizedCertificateFailure(
|
||||
homeServerUrl,
|
||||
throwable.fingerprint
|
||||
)
|
||||
}
|
||||
else ->
|
||||
throw throwable
|
||||
}
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||
|
@ -70,7 +70,6 @@ import im.vector.matrix.android.internal.crypto.model.event.RoomKeyWithHeldConte
|
||||
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.crypto.model.toRest
|
||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
@ -98,6 +97,7 @@ import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
@ -340,11 +340,14 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}
|
||||
|
||||
fun ensureDevice() {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
|
||||
// Open the store
|
||||
cryptoStore.open()
|
||||
// TODO why do that everytime? we should mark that it was done
|
||||
uploadDeviceKeys()
|
||||
// this can throw if no network
|
||||
tryThis {
|
||||
uploadDeviceKeys()
|
||||
}
|
||||
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
// this can throw if no backup
|
||||
tryThis {
|
||||
@ -389,7 +392,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
// } else {
|
||||
|
||||
// Why would we do that? it will be called at end of syn
|
||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||
// }
|
||||
}.fold(
|
||||
{
|
||||
@ -888,7 +891,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
*/
|
||||
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
|
||||
return when (secretName) {
|
||||
MASTER_KEY_SSSS_NAME -> {
|
||||
MASTER_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretMSKGossip(secretValue)
|
||||
true
|
||||
}
|
||||
@ -980,7 +983,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
/**
|
||||
* Upload my user's device keys.
|
||||
*/
|
||||
private suspend fun uploadDeviceKeys(): KeysUploadResponse {
|
||||
private suspend fun uploadDeviceKeys() {
|
||||
if (cryptoStore.getDeviceKeysUploaded()) {
|
||||
Timber.d("Keys already uploaded, nothing to do")
|
||||
return
|
||||
}
|
||||
// Prepare the device keys data to send
|
||||
// Sign it
|
||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||
@ -991,7 +998,9 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
)
|
||||
|
||||
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null)
|
||||
return uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||
uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||
|
||||
cryptoStore.setDeviceKeysUploaded(true)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,4 +433,7 @@ internal interface IMXCryptoStore {
|
||||
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||
fun getGossipingEventsTrail(): List<Event>
|
||||
|
||||
fun setDeviceKeysUploaded(uploaded: Boolean)
|
||||
fun getDeviceKeysUploaded(): Boolean
|
||||
}
|
||||
|
@ -842,6 +842,18 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun setDeviceKeysUploaded(uploaded: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDeviceKeysUploaded(): Boolean {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
// Reset all
|
||||
|
@ -54,7 +54,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
// 0, 1, 2: legacy Riot-Android
|
||||
// 3: migrate to RiotX schema
|
||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 10L
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 11L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
@ -70,6 +70,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
if (oldVersion <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
if (oldVersion <= 10) migrateTo11(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1Legacy(realm: DynamicRealm) {
|
||||
@ -446,4 +447,11 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
|
||||
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
|
||||
}
|
||||
|
||||
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
|
||||
private fun migrateTo11(realm: DynamicRealm) {
|
||||
Timber.d("Step 10 -> 11")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,9 @@ internal open class CryptoMetadataEntity(
|
||||
// The keys backup version currently used. Null means no backup.
|
||||
var backupVersion: String? = null,
|
||||
|
||||
// The device keys has been sent to the homeserver
|
||||
var deviceKeysSentToServer: Boolean = false,
|
||||
|
||||
var xSignMasterPrivateKey: String? = null,
|
||||
var xSignUserPrivateKey: String? = null,
|
||||
var xSignSelfSignedPrivateKey: String? = null,
|
||||
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||
import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException
|
||||
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
@ -106,6 +107,12 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is UnrecognizedCertificateException -> {
|
||||
throw Failure.UnrecognizedCertificateFailure(
|
||||
"https://$domain",
|
||||
throwable.fingerprint
|
||||
)
|
||||
}
|
||||
is Failure.NetworkConnection -> {
|
||||
WellknownResult.Ignore
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ androidExtensions {
|
||||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 91
|
||||
ext.versionPatch = 4
|
||||
ext.versionPatch = 5
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -37,3 +37,11 @@ internal fun String.ensureProtocol(): String {
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
internal fun String.ensureTrailingSlash(): String {
|
||||
return when {
|
||||
isEmpty() -> this
|
||||
!endsWith("/") -> "$this/"
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is Failure.Cancelled ->
|
||||
/* Ignore this error, user has cancelled the action */
|
||||
Unit
|
||||
is Failure.ServerError ->
|
||||
if (throwable.error.code == MatrixError.M_FORBIDDEN
|
||||
&& throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) {
|
||||
|
@ -151,8 +151,8 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
// TODO Disabled because it provokes a flickering
|
||||
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||
})
|
||||
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone()
|
||||
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected()
|
||||
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
|
||||
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
|
||||
is LoginViewEvents.OnLoginFlowRetrieved ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
if (loginViewEvents.isSso) {
|
||||
@ -228,18 +228,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onServerSelectionDone() = withState(loginViewModel) { state ->
|
||||
when (state.serverType) {
|
||||
private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) {
|
||||
when (loginViewEvents.serverType) {
|
||||
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
||||
ServerType.Modular,
|
||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerUrlFormFragment::class.java,
|
||||
option = commonOption)
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
||||
when (state.signMode) {
|
||||
private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state ->
|
||||
// state.signMode could not be ready yet. So use value from the ViewEvent
|
||||
when (loginViewEvents.signMode) {
|
||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||
SignMode.SignUp -> {
|
||||
// This is managed by the LoginViewEvents
|
||||
|
@ -54,6 +54,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
|
||||
private var passwordShown = false
|
||||
private var isSignupMode = false
|
||||
|
||||
// Temporary patch for https://github.com/vector-im/riotX-android/issues/1410,
|
||||
// waiting for https://github.com/matrix-org/synapse/issues/7576
|
||||
private var isNumericOnlyUserIdForbidden = false
|
||||
@ -138,6 +139,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
loginServerIcon.isVisible = false
|
||||
loginTitle.text = getString(R.string.login_signin_matrix_id_title)
|
||||
loginNotice.text = getString(R.string.login_signin_matrix_id_notice)
|
||||
loginPasswordNotice.isVisible = true
|
||||
} else {
|
||||
val resId = when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
@ -164,7 +166,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||
loginNotice.text = getString(R.string.login_server_other_text)
|
||||
}
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
loginPasswordNotice.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ package im.vector.riotx.features.login
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.utils.openUrlInChromeCustomTab
|
||||
import kotlinx.android.synthetic.main.fragment_login_server_selection.*
|
||||
@ -40,11 +39,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||
}
|
||||
|
||||
private fun updateSelectedChoice(state: LoginViewState) {
|
||||
state.serverType.let {
|
||||
loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg
|
||||
loginServerChoiceModular.isChecked = it == ServerType.Modular
|
||||
loginServerChoiceOther.isChecked = it == ServerType.Other
|
||||
}
|
||||
loginServerChoiceMatrixOrg.isChecked = state.serverType == ServerType.MatrixOrg
|
||||
}
|
||||
|
||||
private fun initTextViews() {
|
||||
@ -61,42 +56,17 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||
|
||||
@OnClick(R.id.loginServerChoiceMatrixOrg)
|
||||
fun selectMatrixOrg() {
|
||||
if (loginServerChoiceMatrixOrg.isChecked) {
|
||||
// Consider this is a submit
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
|
||||
}
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerChoiceModular)
|
||||
fun selectModular() {
|
||||
if (loginServerChoiceModular.isChecked) {
|
||||
// Consider this is a submit
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
|
||||
}
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerChoiceOther)
|
||||
fun selectOther() {
|
||||
if (loginServerChoiceOther.isChecked) {
|
||||
// Consider this is a submit
|
||||
submit()
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerSubmit)
|
||||
fun submit() = withState(loginViewModel) { state ->
|
||||
if (state.serverType == ServerType.MatrixOrg) {
|
||||
// Request login flow here
|
||||
loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone))
|
||||
}
|
||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerIKnowMyIdSubmit)
|
||||
|
@ -70,7 +70,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint)
|
||||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_modular_notice)
|
||||
}
|
||||
ServerType.Other -> {
|
||||
else -> {
|
||||
loginServerUrlFormIcon.isVisible = false
|
||||
loginServerUrlFormTitle.text = getString(R.string.login_server_other_title)
|
||||
loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server)
|
||||
@ -78,7 +78,6 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint)
|
||||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_other_notice)
|
||||
}
|
||||
else -> error("This fragment should not be displayed in matrix.org mode")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
|
||||
loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
|
||||
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
|
||||
}
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,9 @@ sealed class LoginViewEvents : VectorViewEvents {
|
||||
// Navigation event
|
||||
|
||||
object OpenServerSelection : LoginViewEvents()
|
||||
object OnServerSelectionDone : LoginViewEvents()
|
||||
data class OnServerSelectionDone(val serverType: ServerType) : LoginViewEvents()
|
||||
data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
|
||||
object OnSignModeSelected : LoginViewEvents()
|
||||
data class OnSignModeSelected(val signMode: SignMode) : LoginViewEvents()
|
||||
object OnForgetPasswordClicked : LoginViewEvents()
|
||||
object OnResetPasswordSendThreePidDone : LoginViewEvents()
|
||||
object OnResetPasswordMailConfirmationSuccess : LoginViewEvents()
|
||||
|
@ -39,6 +39,7 @@ import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.registration.Stage
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.riotx.R
|
||||
@ -47,6 +48,7 @@ import im.vector.riotx.core.extensions.configureAndStart
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.ensureTrailingSlash
|
||||
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||
import im.vector.riotx.features.session.SessionListener
|
||||
@ -87,8 +89,12 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// Store the last action, to redo it after user has trusted the untrusted certificate
|
||||
private var lastAction: LoginAction? = null
|
||||
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
|
||||
|
||||
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||
|
||||
val currentThreePid: String?
|
||||
get() = registrationWizard?.currentThreePid
|
||||
|
||||
@ -111,8 +117,8 @@ class LoginViewModel @AssistedInject constructor(
|
||||
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
||||
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is LoginAction.InitWith -> handleInitWith(action)
|
||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
|
||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
|
||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||
@ -126,10 +132,23 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
||||
// It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow
|
||||
currentHomeServerConnectionConfig
|
||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { getLoginFlow(it) }
|
||||
// It happen when we get the login flow, or during direct authentication.
|
||||
// So alter the homeserver config and retrieve again the login flow
|
||||
when (val finalLastAction = lastAction) {
|
||||
is LoginAction.UpdateHomeServer ->
|
||||
currentHomeServerConnectionConfig
|
||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { getLoginFlow(it) }
|
||||
is LoginAction.LoginOrRegister ->
|
||||
handleDirectLogin(
|
||||
finalLastAction,
|
||||
HomeServerConnectionConfig.Builder()
|
||||
// Will be replaced by the task
|
||||
.withHomeServerUri("https://dummy.org")
|
||||
.withAllowedFingerPrints(listOf(action.fingerprint))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||
@ -321,7 +340,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
LoginAction.ResetHomeServerType -> {
|
||||
setState {
|
||||
copy(
|
||||
serverType = ServerType.MatrixOrg
|
||||
serverType = ServerType.Unknown
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -333,6 +352,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null,
|
||||
loginMode = LoginMode.Unknown,
|
||||
serverType = ServerType.Unknown,
|
||||
loginModeSupportedTypes = emptyList()
|
||||
)
|
||||
}
|
||||
@ -379,9 +399,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
when (action.signMode) {
|
||||
SignMode.SignUp -> startRegistrationFlow()
|
||||
SignMode.SignIn -> startAuthenticationFlow()
|
||||
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
||||
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
|
||||
SignMode.Unknown -> Unit
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
||||
@ -390,6 +410,15 @@ class LoginViewModel @AssistedInject constructor(
|
||||
serverType = action.serverType
|
||||
)
|
||||
}
|
||||
|
||||
when (action.serverType) {
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.MatrixOrg ->
|
||||
// Request login flow here
|
||||
handle(LoginAction.UpdateHomeServer(matrixOrgUrl))
|
||||
ServerType.Modular,
|
||||
ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleInitWith(action: LoginAction.InitWith) {
|
||||
@ -427,7 +456,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// TODO Handled JobCancellationException
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Fail(failure)
|
||||
@ -469,7 +497,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// TODO Handled JobCancellationException
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Fail(failure)
|
||||
@ -485,23 +512,22 @@ class LoginViewModel @AssistedInject constructor(
|
||||
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||
SignMode.SignIn -> handleLogin(action)
|
||||
SignMode.SignUp -> handleRegisterWith(action)
|
||||
SignMode.SignInWithMatrixId -> handleDirectLogin(action)
|
||||
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleDirectLogin(action: LoginAction.LoginOrRegister) {
|
||||
private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
// TODO Handle certificate error in this case. Direct login is deactivated now, so we will handle that later
|
||||
authenticationService.getWellKnownData(action.username, null, object : MatrixCallback<WellknownResult> {
|
||||
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
|
||||
override fun onSuccess(data: WellknownResult) {
|
||||
when (data) {
|
||||
is WellknownResult.Prompt ->
|
||||
onWellknownSuccess(action, data)
|
||||
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||
is WellknownResult.InvalidMatrixId -> {
|
||||
setState {
|
||||
copy(
|
||||
@ -522,23 +548,26 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) {
|
||||
val homeServerConnectionConfig = HomeServerConnectionConfig(
|
||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||
wellKnownPrompt: WellknownResult.Prompt,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
?.copy(
|
||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
?: HomeServerConnectionConfig(
|
||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
|
||||
authenticationService.directAuthentication(
|
||||
homeServerConnectionConfig,
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
@ -548,15 +577,29 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onDirectLoginError(failure: Throwable) {
|
||||
if (failure is Failure.UnrecognizedCertificateFailure) {
|
||||
// Display this error in a dialog
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
||||
val safeLoginWizard = loginWizard
|
||||
|
||||
@ -584,7 +627,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// TODO Handled JobCancellationException
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
@ -609,7 +651,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
// Ensure Wizard is ready
|
||||
loginWizard
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
||||
_viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignIn))
|
||||
}
|
||||
|
||||
private fun onFlowResponse(flowResult: FlowResult) {
|
||||
@ -673,7 +715,10 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
asyncHomeServerLoginFlowRequest = Loading(),
|
||||
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||
)
|
||||
}
|
||||
|
||||
@ -682,7 +727,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
// If we were trying to retrieve matrix.org login flow, also reset the serverType
|
||||
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ data class LoginViewState(
|
||||
|
||||
// User choices
|
||||
@PersistState
|
||||
val serverType: ServerType = ServerType.MatrixOrg,
|
||||
val serverType: ServerType = ServerType.Unknown,
|
||||
@PersistState
|
||||
val signMode: SignMode = SignMode.Unknown,
|
||||
@PersistState
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
enum class ServerType {
|
||||
Unknown,
|
||||
MatrixOrg,
|
||||
Modular,
|
||||
Other
|
||||
|
@ -93,8 +93,9 @@ class SoftLogoutFragment @Inject constructor(
|
||||
softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password))
|
||||
}
|
||||
|
||||
override fun signinFallbackSubmit() {
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected))
|
||||
override fun signinFallbackSubmit() = withState(loginViewModel) { state ->
|
||||
// The loginViewModel has been prepared for a SSO/login fallback recovery (above)
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected(state.signMode)))
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
|
@ -2,6 +2,7 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@drawable/bg_login_server_checked" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/bg_login_server_checked" android:state_pressed="true" />
|
||||
|
||||
<item android:drawable="@drawable/bg_login_server" />
|
||||
|
||||
|
@ -106,6 +106,16 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginPasswordNotice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:text="@string/login_signin_matrix_id_password_notice"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -43,6 +43,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerTitle" />
|
||||
|
||||
<!-- Use a CheckableConstraintLayout to keep the pressed state when retrieving login flow -->
|
||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
||||
android:id="@+id/loginServerChoiceMatrixOrg"
|
||||
android:layout_width="match_parent"
|
||||
@ -84,7 +85,7 @@
|
||||
|
||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
||||
|
||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/loginServerChoiceModular"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -135,9 +136,9 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/loginServerChoiceModularText" />
|
||||
|
||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/loginServerChoiceOther"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -178,45 +179,20 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOtherTitle" />
|
||||
|
||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/loginServerSubmit"
|
||||
style="@style/Style.Vector.Login.Button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/login_continue"
|
||||
android:transitionName="loginSubmitTransition"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginServerIKnowMyIdNotice"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginServerIKnowMyIdNotice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="start"
|
||||
android:text="@string/login_connect_using_matrix_id_notice"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerSubmit" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/loginServerIKnowMyIdSubmit"
|
||||
style="@style/Style.Vector.Login.Button.Text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/login_connect_using_matrix_id_submit"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerIKnowMyIdNotice" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@ -1995,10 +1995,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
||||
</plurals>
|
||||
|
||||
<string name="login_connect_using_matrix_id_notice">Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method:</string>
|
||||
<string name="login_connect_using_matrix_id_submit">Sign in with my Matrix identifier</string>
|
||||
<string name="login_signin_matrix_id_title">Sign in</string>
|
||||
<string name="login_signin_matrix_id_notice">Enter your identifier and your password</string>
|
||||
<string name="login_signin_matrix_id_hint">User identifier</string>
|
||||
<string name="login_connect_using_matrix_id_submit">Sign in with Matrix ID</string>
|
||||
<string name="login_signin_matrix_id_title">Sign in with Matrix ID</string>
|
||||
<string name="login_signin_matrix_id_notice">If you set up an account on a homeserver, use your Matrix ID (e.g. @user:domain.com) and password below.</string>
|
||||
<string name="login_signin_matrix_id_hint">Matrix ID</string>
|
||||
<string name="login_signin_matrix_id_password_notice">If you don’t know your password, go back to reset it.</string>
|
||||
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
|
||||
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user