Merge branch 'develop' into feature/some_upgrade

This commit is contained in:
Benoit Marty 2020-07-06 23:38:26 +02:00 committed by GitHub
commit b7d86c3fa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 239 additions and 141 deletions

View File

@ -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)
===================================================

View File

@ -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)

View File

@ -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)
}
/**

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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
}

View File

@ -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'

View File

@ -37,3 +37,11 @@ internal fun String.ensureProtocol(): String {
else -> this
}
}
internal fun String.ensureTrailingSlash(): String {
return when {
isEmpty() -> this
!endsWith("/") -> "$this/"
else -> this
}
}

View File

@ -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 */) {

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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 */
}
}

View File

@ -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()

View File

@ -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
)
}
}

View File

@ -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

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.login
enum class ServerType {
Unknown,
MatrixOrg,
Modular,
Other

View File

@ -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() {

View File

@ -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" />

View File

@ -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"

View File

@ -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>

View File

@ -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 dont 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>