Merge branch 'develop' into feature/fix_version
This commit is contained in:
		
						commit
						d402b49f07
					
				@ -8,11 +8,19 @@ Features ✨:
 | 
			
		||||
Improvements 🙌:
 | 
			
		||||
 - New wording for notice when current user is the sender
 | 
			
		||||
 - Hide "X made no changes" event by default in timeline (#1430)
 | 
			
		||||
 - Hide left rooms in breadcrumbs (#766)
 | 
			
		||||
 - Correctly handle SSO login redirection
 | 
			
		||||
 - SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400)
 | 
			
		||||
 - Improve checking of homeserver version support (#1442)
 | 
			
		||||
 | 
			
		||||
Bugfix 🐛:
 | 
			
		||||
 - Switch theme is not fully taken into account without restarting the app
 | 
			
		||||
 - Temporary fix to show error when user is creating an account on matrix.org with userId containing only digits (#1410)
 | 
			
		||||
 - Reply composer overlay stays on screen too long after send (#1169)
 | 
			
		||||
 - Fix navigation bar icon contrast on API in [21,27[ (#1342)
 | 
			
		||||
 - Fix status bar icon contrast on API in [21,23[
 | 
			
		||||
 - Wrong /query request (#1444)
 | 
			
		||||
 - Make Credentials.homeServer optional because it is deprecated (#1443)
 | 
			
		||||
 | 
			
		||||
Translations 🗣:
 | 
			
		||||
 -
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
 | 
			
		||||
 | 
			
		||||
## Sign up flows
 | 
			
		||||
## Sign in flows
 | 
			
		||||
 | 
			
		||||
### Get the flow
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@ We get credential (200)
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "user_id": "@alice:matrix.org",
 | 
			
		||||
  "access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
 | 
			
		||||
  "access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lr",
 | 
			
		||||
  "home_server": "matrix.org",
 | 
			
		||||
  "device_id": "GTVREDALBF",
 | 
			
		||||
  "well_known": {
 | 
			
		||||
@ -117,7 +117,7 @@ We get the credentials (200)
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "user_id": "@alice:matrix.org",
 | 
			
		||||
  "access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
 | 
			
		||||
  "access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3Jnfrfdegfszsefddvf",
 | 
			
		||||
  "home_server": "matrix.org",
 | 
			
		||||
  "device_id": "WBSREDASND",
 | 
			
		||||
  "well_known": {
 | 
			
		||||
@ -145,12 +145,59 @@ Not supported yet in RiotX
 | 
			
		||||
  "flows": [
 | 
			
		||||
    {
 | 
			
		||||
      "type": "m.login.sso"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "m.login.token"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
 | 
			
		||||
In this case, the user can click on "Sign in with SSO" and the native web browser, or a ChromeCustomTab if the device supports it, will be launched on the page
 | 
			
		||||
 | 
			
		||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=riotx%3A%2F%2Friotx
 | 
			
		||||
 | 
			
		||||
The parameter `redirectUrl` is set to `riotx://riotx`.
 | 
			
		||||
 | 
			
		||||
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
 | 
			
		||||
 | 
			
		||||
The browser will then take care of the SSO login, which may include creating a third party account, entering an email, settings a display name, or any other possibilities.
 | 
			
		||||
 | 
			
		||||
During the process, user may be asked to validate an email by clicking on a link it contains. The link has to be opened in the browser which initiates the authentication. This is why we cannot use WebView anymore.
 | 
			
		||||
 | 
			
		||||
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
 | 
			
		||||
 | 
			
		||||
> riotx://riotx?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
 | 
			
		||||
 | 
			
		||||
This navigation is intercepted by RiotX by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
 | 
			
		||||
 | 
			
		||||
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "type": "m.login.token",
 | 
			
		||||
  "token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We get the credentials (200)
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "user_id": "@alice:homeserver.with.sso",
 | 
			
		||||
  "access_token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVyIGtleQowMDEwY2lkIGdlbiA9IDEKMDAyY2NpZCB1c2",
 | 
			
		||||
  "home_server": "homeserver.with.sso",
 | 
			
		||||
  "device_id": "DETBTVAHCH",
 | 
			
		||||
  "well_known": {
 | 
			
		||||
    "m.homeserver": {
 | 
			
		||||
      "base_url": "https:\/\/homeserver.with.sso\/"
 | 
			
		||||
    },
 | 
			
		||||
    "m.identity_server": {
 | 
			
		||||
      "base_url": "https:\/\/vector.im"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Reset password
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
 | 
			
		||||
import im.vector.matrix.android.api.session.sync.SyncState
 | 
			
		||||
import im.vector.matrix.android.api.session.user.model.User
 | 
			
		||||
import im.vector.matrix.android.api.session.widgets.model.Widget
 | 
			
		||||
import im.vector.matrix.android.api.util.JsonDict
 | 
			
		||||
import im.vector.matrix.android.api.util.Optional
 | 
			
		||||
import im.vector.matrix.android.api.util.toOptional
 | 
			
		||||
@ -36,7 +37,6 @@ import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
 | 
			
		||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
 | 
			
		||||
import im.vector.matrix.android.api.session.widgets.model.Widget
 | 
			
		||||
import io.reactivex.Observable
 | 
			
		||||
import io.reactivex.Single
 | 
			
		||||
 | 
			
		||||
@ -56,10 +56,10 @@ class RxSession(private val session: Session) {
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
 | 
			
		||||
        return session.getBreadcrumbsLive().asObservable()
 | 
			
		||||
    fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
 | 
			
		||||
        return session.getBreadcrumbsLive(queryParams).asObservable()
 | 
			
		||||
                .startWithCallable {
 | 
			
		||||
                    session.getBreadcrumbs()
 | 
			
		||||
                    session.getBreadcrumbs(queryParams)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,6 @@ const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
 | 
			
		||||
 * Path to use when the client want to connect using SSO
 | 
			
		||||
 * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
 | 
			
		||||
 */
 | 
			
		||||
const val SSO_FALLBACK_PATH = "/_matrix/client/r0/login/sso/redirect"
 | 
			
		||||
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
 | 
			
		||||
 | 
			
		||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ data class Credentials(
 | 
			
		||||
         * @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
 | 
			
		||||
         * if they require it. Note also that homeserver is not spelt this way.
 | 
			
		||||
         */
 | 
			
		||||
        @Json(name = "home_server") val homeServer: String,
 | 
			
		||||
        @Json(name = "home_server") val homeServer: String?,
 | 
			
		||||
        /**
 | 
			
		||||
         * ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
@ -18,10 +18,10 @@ package im.vector.matrix.android.api.auth.data
 | 
			
		||||
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
 | 
			
		||||
 | 
			
		||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
 | 
			
		||||
// Either a list of supported login types, or an error if the homeserver is outdated
 | 
			
		||||
sealed class LoginFlowResult {
 | 
			
		||||
    data class Success(
 | 
			
		||||
            val loginFlowResponse: LoginFlowResponse,
 | 
			
		||||
            val supportedLoginTypes: List<String>,
 | 
			
		||||
            val isLoginAndRegistrationSupported: Boolean,
 | 
			
		||||
            val homeServerUrl: String
 | 
			
		||||
    ) : LoginFlowResult()
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package im.vector.matrix.android.internal.auth.data
 | 
			
		||||
package im.vector.matrix.android.api.auth.data
 | 
			
		||||
 | 
			
		||||
object LoginFlowTypes {
 | 
			
		||||
    const val PASSWORD = "m.login.password"
 | 
			
		||||
@ -34,6 +34,12 @@ interface LoginWizard {
 | 
			
		||||
              deviceName: String,
 | 
			
		||||
              callback: MatrixCallback<Session>): Cancelable
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exchange a login token to an access token
 | 
			
		||||
     */
 | 
			
		||||
    fun loginWithToken(loginToken: String,
 | 
			
		||||
                       callback: MatrixCallback<Session>): Cancelable
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reset user password
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -21,15 +21,12 @@ sealed class Stage(open val mandatory: Boolean) {
 | 
			
		||||
    // m.login.recaptcha
 | 
			
		||||
    data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
 | 
			
		||||
 | 
			
		||||
    // m.login.oauth2
 | 
			
		||||
    // m.login.email.identity
 | 
			
		||||
    data class Email(override val mandatory: Boolean) : Stage(mandatory)
 | 
			
		||||
 | 
			
		||||
    // m.login.msisdn
 | 
			
		||||
    data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
 | 
			
		||||
 | 
			
		||||
    // m.login.token
 | 
			
		||||
 | 
			
		||||
    // m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
 | 
			
		||||
    // and a password, the dummy stage has to be done
 | 
			
		||||
    data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
 | 
			
		||||
 | 
			
		||||
@ -73,15 +73,17 @@ interface RoomService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a snapshot list of Breadcrumbs
 | 
			
		||||
     * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
 | 
			
		||||
     * @return the immutable list of [RoomSummary]
 | 
			
		||||
     */
 | 
			
		||||
    fun getBreadcrumbs(): List<RoomSummary>
 | 
			
		||||
    fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a live list of Breadcrumbs
 | 
			
		||||
     * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
 | 
			
		||||
     * @return the [LiveData] of [RoomSummary]
 | 
			
		||||
     */
 | 
			
		||||
    fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
 | 
			
		||||
    fun getBreadcrumbsLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inform the Matrix SDK that a room is displayed.
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
 | 
			
		||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
 | 
			
		||||
@ -54,7 +55,7 @@ internal interface AuthAPI {
 | 
			
		||||
    fun versions(): Call<Versions>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register to the homeserver
 | 
			
		||||
     * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
 | 
			
		||||
     * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
 | 
			
		||||
     */
 | 
			
		||||
    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
 | 
			
		||||
@ -91,6 +92,11 @@ internal interface AuthAPI {
 | 
			
		||||
    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
 | 
			
		||||
    fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
 | 
			
		||||
 | 
			
		||||
    // Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams
 | 
			
		||||
    @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
 | 
			
		||||
    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
 | 
			
		||||
    fun login(@Body loginParams: TokenLoginParams): Call<Credentials>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ask the homeserver to reset the password associated with the provided email.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -236,7 +236,7 @@ internal class DefaultAuthenticationService @Inject constructor(
 | 
			
		||||
            val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
 | 
			
		||||
                apiCall = authAPI.getLoginFlows()
 | 
			
		||||
            }
 | 
			
		||||
            LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
 | 
			
		||||
            LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Not supported
 | 
			
		||||
            LoginFlowResult.OutdatedHomeserver
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,19 @@ import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
data class LoginFlowResponse(
 | 
			
		||||
internal data class LoginFlowResponse(
 | 
			
		||||
        /**
 | 
			
		||||
         * The homeserver's supported login types
 | 
			
		||||
         */
 | 
			
		||||
        @Json(name = "flows")
 | 
			
		||||
        val flows: List<InteractiveAuthenticationFlow>
 | 
			
		||||
        val flows: List<LoginFlow>?
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
internal data class LoginFlow(
 | 
			
		||||
        /**
 | 
			
		||||
         * The login type. This is supplied as the type when logging in.
 | 
			
		||||
         */
 | 
			
		||||
        @Json(name = "type")
 | 
			
		||||
        val type: String?
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ref:
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.matrix.android.internal.auth.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
internal data class TokenLoginParams(
 | 
			
		||||
        @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
 | 
			
		||||
        @Json(name = "token") val token: String
 | 
			
		||||
) : LoginParams
 | 
			
		||||
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
 | 
			
		||||
import im.vector.matrix.android.internal.auth.SessionCreator
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
 | 
			
		||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
 | 
			
		||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
 | 
			
		||||
@ -65,6 +66,22 @@ internal class DefaultLoginWizard(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
 | 
			
		||||
     */
 | 
			
		||||
    override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
 | 
			
		||||
        return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
 | 
			
		||||
            val loginParams = TokenLoginParams(
 | 
			
		||||
                    token = loginToken
 | 
			
		||||
            )
 | 
			
		||||
            val credentials = executeRequest<Credentials>(null) {
 | 
			
		||||
                apiCall = authAPI.login(loginParams)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun loginInternal(login: String,
 | 
			
		||||
                                      password: String,
 | 
			
		||||
                                      deviceName: String) = withContext(coroutineDispatchers.computation) {
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.auth.registration
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Open class, parent to all possible authentication parameters
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.registration
 | 
			
		||||
 | 
			
		||||
import dagger.Lazy
 | 
			
		||||
import im.vector.matrix.android.api.MatrixCallback
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
 | 
			
		||||
@ -28,7 +29,6 @@ import im.vector.matrix.android.api.util.NoOpCancellable
 | 
			
		||||
import im.vector.matrix.android.internal.auth.AuthAPI
 | 
			
		||||
import im.vector.matrix.android.internal.auth.PendingSessionStore
 | 
			
		||||
import im.vector.matrix.android.internal.auth.SessionCreator
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
 | 
			
		||||
import im.vector.matrix.android.internal.network.RetrofitFactory
 | 
			
		||||
import im.vector.matrix.android.internal.task.launchToCallback
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.FlowResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.Stage
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.TermPolicies
 | 
			
		||||
import im.vector.matrix.android.api.util.JsonDict
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
data class RegistrationFlowResponse(
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ internal data class KeysQueryBody(
 | 
			
		||||
         * A map from user ID, to a list of device IDs, or to an empty list to indicate all devices for the corresponding user.
 | 
			
		||||
         */
 | 
			
		||||
        @Json(name = "device_keys")
 | 
			
		||||
        val deviceKeys: Map<String, Any>,
 | 
			
		||||
        val deviceKeys: Map<String, List<String>>,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * If the client is fetching keys as a result of a device update received in a sync request, this should be the 'since' token
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.model.rest
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class provides the authentication data by using user and password
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
package im.vector.matrix.android.internal.crypto.tasks
 | 
			
		||||
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import javax.inject.Inject
 | 
			
		||||
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
 | 
			
		||||
    data class Params(
 | 
			
		||||
            // the list of users to get keys for.
 | 
			
		||||
            val userIds: List<String>?,
 | 
			
		||||
            val userIds: List<String>,
 | 
			
		||||
            // the up-to token
 | 
			
		||||
            val token: String?
 | 
			
		||||
    )
 | 
			
		||||
@ -39,7 +39,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
 | 
			
		||||
) : DownloadKeysForUsersTask {
 | 
			
		||||
 | 
			
		||||
    override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
 | 
			
		||||
        val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
 | 
			
		||||
        val downloadQuery = params.userIds.associateWith { emptyList<String>() }
 | 
			
		||||
 | 
			
		||||
        val body = KeysQueryBody(
 | 
			
		||||
                deviceKeys = downloadQuery,
 | 
			
		||||
 | 
			
		||||
@ -111,24 +111,22 @@ internal class DefaultRoomService @Inject constructor(
 | 
			
		||||
        return query
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getBreadcrumbs(): List<RoomSummary> {
 | 
			
		||||
    override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
 | 
			
		||||
        return monarchy.fetchAllMappedSync(
 | 
			
		||||
                { breadcrumbsQuery(it) },
 | 
			
		||||
                { breadcrumbsQuery(it, queryParams) },
 | 
			
		||||
                { roomSummaryMapper.map(it) }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getBreadcrumbsLive(): LiveData<List<RoomSummary>> {
 | 
			
		||||
    override fun getBreadcrumbsLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
 | 
			
		||||
        return monarchy.findAllMappedWithChanges(
 | 
			
		||||
                { breadcrumbsQuery(it) },
 | 
			
		||||
                { breadcrumbsQuery(it, queryParams) },
 | 
			
		||||
                { roomSummaryMapper.map(it) }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun breadcrumbsQuery(realm: Realm): RealmQuery<RoomSummaryEntity> {
 | 
			
		||||
        return RoomSummaryEntity.where(realm)
 | 
			
		||||
                .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
 | 
			
		||||
                .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
 | 
			
		||||
    private fun breadcrumbsQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
 | 
			
		||||
        return roomSummariesQuery(realm, queryParams)
 | 
			
		||||
                .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
 | 
			
		||||
                .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ internal interface WidgetsAPI {
 | 
			
		||||
    /**
 | 
			
		||||
     * register to the server
 | 
			
		||||
     *
 | 
			
		||||
     * @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
 | 
			
		||||
     * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
 | 
			
		||||
     */
 | 
			
		||||
    @POST("register")
 | 
			
		||||
    fun register(@Body body: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>
 | 
			
		||||
 | 
			
		||||
@ -332,6 +332,9 @@ dependencies {
 | 
			
		||||
    implementation 'com.google.android:flexbox:1.1.1'
 | 
			
		||||
    implementation "androidx.autofill:autofill:$autofill_version"
 | 
			
		||||
 | 
			
		||||
    // Custom Tab
 | 
			
		||||
    implementation 'androidx.browser:browser:1.2.0'
 | 
			
		||||
 | 
			
		||||
    // Passphrase strength helper
 | 
			
		||||
    implementation 'com.nulab-inc:zxcvbn:1.2.7'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,19 @@
 | 
			
		||||
        <activity android:name=".features.home.HomeActivity" />
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".features.login.LoginActivity"
 | 
			
		||||
            android:windowSoftInputMode="adjustResize" />
 | 
			
		||||
            android:launchMode="singleTask"
 | 
			
		||||
            android:windowSoftInputMode="adjustResize">
 | 
			
		||||
            <!-- Add intent filter to handle redirection URL after SSO login in external browser -->
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.VIEW" />
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
                <category android:name="android.intent.category.BROWSABLE" />
 | 
			
		||||
 | 
			
		||||
                <data android:scheme="riotx" />
 | 
			
		||||
                <data android:host="riotx" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
        <activity android:name=".features.media.ImageMediaViewerActivity" />
 | 
			
		||||
        <activity android:name=".features.media.BigImageViewerActivity" />
 | 
			
		||||
        <activity
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,7 @@ import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginServerSelectionFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginServerUrlFormFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginSignUpSignInSsoFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginSplashFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginWaitForEmailFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginWebFragment
 | 
			
		||||
@ -217,6 +218,11 @@ interface FragmentModule {
 | 
			
		||||
    @FragmentKey(LoginSignUpSignInSelectionFragment::class)
 | 
			
		||||
    fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment
 | 
			
		||||
 | 
			
		||||
    @Binds
 | 
			
		||||
    @IntoMap
 | 
			
		||||
    @FragmentKey(LoginSignUpSignInSsoFragment::class)
 | 
			
		||||
    fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
 | 
			
		||||
 | 
			
		||||
    @Binds
 | 
			
		||||
    @IntoMap
 | 
			
		||||
    @FragmentKey(LoginSplashFragment::class)
 | 
			
		||||
 | 
			
		||||
@ -21,10 +21,14 @@ import android.content.ActivityNotFoundException
 | 
			
		||||
import android.content.ContentValues
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.graphics.BitmapFactory
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.provider.Browser
 | 
			
		||||
import android.provider.MediaStore
 | 
			
		||||
import androidx.browser.customtabs.CustomTabsIntent
 | 
			
		||||
import androidx.browser.customtabs.CustomTabsSession
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.core.content.FileProvider
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import im.vector.riotx.BuildConfig
 | 
			
		||||
@ -64,6 +68,34 @@ fun openUrlInExternalBrowser(context: Context, uri: Uri?) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Open url in custom tab or, if not available, in the default browser
 | 
			
		||||
 * If several compatible browsers are installed, the user will be proposed to choose one.
 | 
			
		||||
 * Ref: https://developer.chrome.com/multidevice/android/customtabs
 | 
			
		||||
 */
 | 
			
		||||
fun openUrlInChromeCustomTab(context: Context, session: CustomTabsSession?, url: String) {
 | 
			
		||||
    try {
 | 
			
		||||
        CustomTabsIntent.Builder()
 | 
			
		||||
                .setToolbarColor(ContextCompat.getColor(context, R.color.riotx_background_light))
 | 
			
		||||
                .apply {
 | 
			
		||||
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
 | 
			
		||||
                        setNavigationBarColor(ContextCompat.getColor(context, R.color.riotx_header_panel_background_light))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .setNavigationBarColor(ContextCompat.getColor(context, R.color.riotx_background_light))
 | 
			
		||||
                .setColorScheme(CustomTabsIntent.COLOR_SCHEME_LIGHT)
 | 
			
		||||
                // Note: setting close button icon does not work
 | 
			
		||||
                .setCloseButtonIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_back_24dp))
 | 
			
		||||
                .setStartAnimations(context, R.anim.enter_fade_in, R.anim.exit_fade_out)
 | 
			
		||||
                .setExitAnimations(context, R.anim.enter_fade_in, R.anim.exit_fade_out)
 | 
			
		||||
                .apply { session?.let { setSession(it) } }
 | 
			
		||||
                .build()
 | 
			
		||||
                .launchUrl(context, Uri.parse(url))
 | 
			
		||||
    } catch (activityNotFoundException: ActivityNotFoundException) {
 | 
			
		||||
        context.toast(R.string.error_no_external_application_found)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Open sound recorder external application
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
package im.vector.riotx.features.crypto.recover
 | 
			
		||||
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.failure.Failure
 | 
			
		||||
import im.vector.matrix.android.api.failure.MatrixError
 | 
			
		||||
import im.vector.matrix.android.api.failure.toRegistrationFlowResponse
 | 
			
		||||
@ -28,7 +29,6 @@ import im.vector.matrix.android.api.session.securestorage.EmptyKeySigner
 | 
			
		||||
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
 | 
			
		||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
 | 
			
		||||
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,10 @@ 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.query.QueryStringValue
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.Membership
 | 
			
		||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 | 
			
		||||
import im.vector.matrix.rx.rx
 | 
			
		||||
import im.vector.riotx.core.platform.EmptyAction
 | 
			
		||||
import im.vector.riotx.core.platform.EmptyViewEvents
 | 
			
		||||
@ -58,7 +61,10 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B
 | 
			
		||||
 | 
			
		||||
    private fun observeBreadcrumbs() {
 | 
			
		||||
        session.rx()
 | 
			
		||||
                .liveBreadcrumbs()
 | 
			
		||||
                .liveBreadcrumbs(roomSummaryQueryParams {
 | 
			
		||||
                    displayName = QueryStringValue.NoCondition
 | 
			
		||||
                    memberships = listOf(Membership.JOIN)
 | 
			
		||||
                })
 | 
			
		||||
                .observeOn(Schedulers.computation())
 | 
			
		||||
                .execute { asyncBreadcrumbs ->
 | 
			
		||||
                    copy(asyncBreadcrumbs = asyncBreadcrumbs)
 | 
			
		||||
 | 
			
		||||
@ -672,6 +672,8 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (text.isNotBlank()) {
 | 
			
		||||
                    // We collapse ASAP, if not there will be a slight anoying delay
 | 
			
		||||
                    composerLayout.collapse(true)
 | 
			
		||||
                    lockSendButton = true
 | 
			
		||||
                    roomDetailViewModel.handle(RoomDetailAction.SendMessage(text, vectorPreferences.isMarkdownEnabled()))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ sealed class LoginAction : VectorViewModelAction {
 | 
			
		||||
    data class UpdateServerType(val serverType: ServerType) : LoginAction()
 | 
			
		||||
    data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
 | 
			
		||||
    data class UpdateSignMode(val signMode: SignMode) : LoginAction()
 | 
			
		||||
    data class LoginWithToken(val loginToken: String) : LoginAction()
 | 
			
		||||
    data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
 | 
			
		||||
    data class InitWith(val loginConfig: LoginConfig) : LoginAction()
 | 
			
		||||
    data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ import com.airbnb.mvrx.viewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.FlowResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.Stage
 | 
			
		||||
import im.vector.matrix.android.api.extensions.tryThis
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
 | 
			
		||||
@ -155,7 +156,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
 | 
			
		||||
            is LoginViewEvents.OnSignModeSelected                         -> onSignModeSelected()
 | 
			
		||||
            is LoginViewEvents.OnLoginFlowRetrieved                       ->
 | 
			
		||||
                addFragmentToBackstack(R.id.loginFragmentContainer,
 | 
			
		||||
                        LoginSignUpSignInSelectionFragment::class.java,
 | 
			
		||||
                        if (loginViewEvents.isSso) {
 | 
			
		||||
                            LoginSignUpSignInSsoFragment::class.java
 | 
			
		||||
                        } else {
 | 
			
		||||
                            LoginSignUpSignInSelectionFragment::class.java
 | 
			
		||||
                        },
 | 
			
		||||
                        option = commonOption)
 | 
			
		||||
            is LoginViewEvents.OnWebLoginError                            -> onWebLoginError(loginViewEvents)
 | 
			
		||||
            is LoginViewEvents.OnForgetPasswordClicked                    ->
 | 
			
		||||
@ -239,16 +244,14 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
 | 
			
		||||
            SignMode.SignIn             -> {
 | 
			
		||||
                // It depends on the LoginMode
 | 
			
		||||
                when (state.loginMode) {
 | 
			
		||||
                    LoginMode.Unknown     -> error("Developer error")
 | 
			
		||||
                    LoginMode.Unknown,
 | 
			
		||||
                    LoginMode.Sso         -> error("Developer error")
 | 
			
		||||
                    LoginMode.Password    -> addFragmentToBackstack(R.id.loginFragmentContainer,
 | 
			
		||||
                            LoginFragment::class.java,
 | 
			
		||||
                            tag = FRAGMENT_LOGIN_TAG,
 | 
			
		||||
                            option = commonOption)
 | 
			
		||||
                    LoginMode.Sso         -> addFragmentToBackstack(R.id.loginFragmentContainer,
 | 
			
		||||
                            LoginWebFragment::class.java,
 | 
			
		||||
                            option = commonOption)
 | 
			
		||||
                    LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
 | 
			
		||||
                }
 | 
			
		||||
                }.exhaustive
 | 
			
		||||
            }
 | 
			
		||||
            SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer,
 | 
			
		||||
                    LoginFragment::class.java,
 | 
			
		||||
@ -257,6 +260,17 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
 | 
			
		||||
        }.exhaustive
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the SSO redirection here
 | 
			
		||||
     */
 | 
			
		||||
    override fun onNewIntent(intent: Intent?) {
 | 
			
		||||
        super.onNewIntent(intent)
 | 
			
		||||
 | 
			
		||||
        intent?.data
 | 
			
		||||
                ?.let { tryThis { it.getQueryParameter("loginToken") } }
 | 
			
		||||
                ?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun onRegistrationStageNotSupported() {
 | 
			
		||||
        AlertDialog.Builder(this)
 | 
			
		||||
                .setTitle(R.string.app_name)
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OnClick(R.id.loginServerChoiceModularLearnMore)
 | 
			
		||||
    fun learMore() {
 | 
			
		||||
    fun learnMore() {
 | 
			
		||||
        openUrlInExternalBrowser(requireActivity(), MODULAR_LINK)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -113,7 +113,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
 | 
			
		||||
 | 
			
		||||
        if (state.loginMode != LoginMode.Unknown) {
 | 
			
		||||
            // LoginFlow for matrix.org has been retrieved
 | 
			
		||||
            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved))
 | 
			
		||||
            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
 | 
			
		||||
 | 
			
		||||
        if (state.loginMode != LoginMode.Unknown) {
 | 
			
		||||
            // The home server url is valid
 | 
			
		||||
            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved))
 | 
			
		||||
            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,13 +26,11 @@ import javax.inject.Inject
 | 
			
		||||
/**
 | 
			
		||||
 * In this screen, the user is asked to sign up or to sign in to the homeserver
 | 
			
		||||
 */
 | 
			
		||||
class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
 | 
			
		||||
open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection
 | 
			
		||||
 | 
			
		||||
    private var isSsoSignIn: Boolean = false
 | 
			
		||||
 | 
			
		||||
    private fun setupUi(state: LoginViewState) {
 | 
			
		||||
    protected fun setupUi(state: LoginViewState) {
 | 
			
		||||
        when (state.serverType) {
 | 
			
		||||
            ServerType.MatrixOrg -> {
 | 
			
		||||
                loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
 | 
			
		||||
@ -54,25 +52,14 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupButtons(state: LoginViewState) {
 | 
			
		||||
        isSsoSignIn = state.loginMode == LoginMode.Sso
 | 
			
		||||
 | 
			
		||||
        if (isSsoSignIn) {
 | 
			
		||||
            loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
 | 
			
		||||
            loginSignupSigninSignIn.isVisible = false
 | 
			
		||||
        } else {
 | 
			
		||||
            loginSignupSigninSubmit.text = getString(R.string.login_signup)
 | 
			
		||||
            loginSignupSigninSignIn.isVisible = true
 | 
			
		||||
        }
 | 
			
		||||
    private fun setupButtons() {
 | 
			
		||||
        loginSignupSigninSubmit.text = getString(R.string.login_signup)
 | 
			
		||||
        loginSignupSigninSignIn.isVisible = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OnClick(R.id.loginSignupSigninSubmit)
 | 
			
		||||
    fun signUp() {
 | 
			
		||||
        if (isSsoSignIn) {
 | 
			
		||||
            signIn()
 | 
			
		||||
        } else {
 | 
			
		||||
            loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
 | 
			
		||||
        }
 | 
			
		||||
    open fun submit() {
 | 
			
		||||
        loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OnClick(R.id.loginSignupSigninSignIn)
 | 
			
		||||
@ -86,6 +73,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
 | 
			
		||||
 | 
			
		||||
    override fun updateWithState(state: LoginViewState) {
 | 
			
		||||
        setupUi(state)
 | 
			
		||||
        setupButtons(state)
 | 
			
		||||
        setupButtons()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.login
 | 
			
		||||
 | 
			
		||||
import android.content.ComponentName
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.browser.customtabs.CustomTabsClient
 | 
			
		||||
import androidx.browser.customtabs.CustomTabsServiceConnection
 | 
			
		||||
import androidx.browser.customtabs.CustomTabsSession
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.utils.openUrlInChromeCustomTab
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In this screen, the user is asked to sign up or to sign in using SSO
 | 
			
		||||
 * This Fragment binds a CustomTabsServiceConnection if available, then prefetch the SSO url, as it will be likely to be opened.
 | 
			
		||||
 */
 | 
			
		||||
open class LoginSignUpSignInSsoFragment @Inject constructor() : LoginSignUpSignInSelectionFragment() {
 | 
			
		||||
 | 
			
		||||
    private var ssoUrl: String? = null
 | 
			
		||||
    private var customTabsServiceConnection: CustomTabsServiceConnection? = null
 | 
			
		||||
    private var customTabsClient: CustomTabsClient? = null
 | 
			
		||||
    private var customTabsSession: CustomTabsSession? = null
 | 
			
		||||
 | 
			
		||||
    override fun onStart() {
 | 
			
		||||
        super.onStart()
 | 
			
		||||
 | 
			
		||||
        val packageName = CustomTabsClient.getPackageName(requireContext(), null)
 | 
			
		||||
 | 
			
		||||
        // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
 | 
			
		||||
        if (packageName != null) {
 | 
			
		||||
            customTabsServiceConnection = object : CustomTabsServiceConnection() {
 | 
			
		||||
                override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
 | 
			
		||||
                    customTabsClient = client
 | 
			
		||||
                            .also { it.warmup(0L) }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onServiceDisconnected(name: ComponentName?) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
                    .also {
 | 
			
		||||
                        CustomTabsClient.bindCustomTabsService(
 | 
			
		||||
                                requireContext(),
 | 
			
		||||
                                // Despite the API, packageName cannot be null
 | 
			
		||||
                                packageName,
 | 
			
		||||
                                it
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun prefetchUrl(url: String) {
 | 
			
		||||
        if (ssoUrl != null) return
 | 
			
		||||
 | 
			
		||||
        ssoUrl = url
 | 
			
		||||
        if (customTabsSession == null) {
 | 
			
		||||
            customTabsSession = customTabsClient?.newSession(null)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onStop() {
 | 
			
		||||
        super.onStop()
 | 
			
		||||
        customTabsServiceConnection?.let { requireContext().unbindService(it) }
 | 
			
		||||
        customTabsServiceConnection = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupButtons() {
 | 
			
		||||
        loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
 | 
			
		||||
        loginSignupSigninSignIn.isVisible = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun submit() {
 | 
			
		||||
        ssoUrl?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateWithState(state: LoginViewState) {
 | 
			
		||||
        setupUi(state)
 | 
			
		||||
        setupButtons()
 | 
			
		||||
        prefetchUrl(state.getSsoUrl())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -34,7 +34,7 @@ sealed class LoginViewEvents : VectorViewEvents {
 | 
			
		||||
 | 
			
		||||
    object OpenServerSelection : LoginViewEvents()
 | 
			
		||||
    object OnServerSelectionDone : LoginViewEvents()
 | 
			
		||||
    object OnLoginFlowRetrieved : LoginViewEvents()
 | 
			
		||||
    data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
 | 
			
		||||
    object OnSignModeSelected : LoginViewEvents()
 | 
			
		||||
    object OnForgetPasswordClicked : LoginViewEvents()
 | 
			
		||||
    object OnResetPasswordSendThreePidDone : LoginViewEvents()
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.MatrixCallback
 | 
			
		||||
import im.vector.matrix.android.api.auth.AuthenticationService
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.auth.login.LoginWizard
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.FlowResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
 | 
			
		||||
@ -40,7 +41,6 @@ import im.vector.matrix.android.api.auth.registration.Stage
 | 
			
		||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.util.Cancelable
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ActiveSessionHolder
 | 
			
		||||
@ -110,6 +110,7 @@ class LoginViewModel @AssistedInject constructor(
 | 
			
		||||
            is LoginAction.InitWith                   -> handleInitWith(action)
 | 
			
		||||
            is LoginAction.UpdateHomeServer           -> handleUpdateHomeserver(action)
 | 
			
		||||
            is LoginAction.LoginOrRegister            -> handleLoginOrRegister(action)
 | 
			
		||||
            is LoginAction.LoginWithToken             -> handleLoginWithToken(action)
 | 
			
		||||
            is LoginAction.WebLoginSuccess            -> handleWebLoginSuccess(action)
 | 
			
		||||
            is LoginAction.ResetPassword              -> handleResetPassword(action)
 | 
			
		||||
            is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
 | 
			
		||||
@ -120,6 +121,41 @@ class LoginViewModel @AssistedInject constructor(
 | 
			
		||||
        }.exhaustive
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
 | 
			
		||||
        val safeLoginWizard = loginWizard
 | 
			
		||||
 | 
			
		||||
        if (safeLoginWizard == null) {
 | 
			
		||||
            setState {
 | 
			
		||||
                copy(
 | 
			
		||||
                        asyncLoginAction = Fail(Throwable("Bad configuration"))
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            setState {
 | 
			
		||||
                copy(
 | 
			
		||||
                        asyncLoginAction = Loading()
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            currentTask = safeLoginWizard.loginWithToken(
 | 
			
		||||
                    action.loginToken,
 | 
			
		||||
                    object : MatrixCallback<Session> {
 | 
			
		||||
                        override fun onSuccess(data: Session) {
 | 
			
		||||
                            onSessionCreated(data)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onFailure(failure: Throwable) {
 | 
			
		||||
                            _viewEvents.post(LoginViewEvents.Failure(failure))
 | 
			
		||||
                            setState {
 | 
			
		||||
                                copy(
 | 
			
		||||
                                        asyncLoginAction = Fail(failure)
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
 | 
			
		||||
        setState {
 | 
			
		||||
            copy(
 | 
			
		||||
@ -635,9 +671,9 @@ class LoginViewModel @AssistedInject constructor(
 | 
			
		||||
                        is LoginFlowResult.Success            -> {
 | 
			
		||||
                            val loginMode = when {
 | 
			
		||||
                                // SSO login is taken first
 | 
			
		||||
                                data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.SSO }      -> LoginMode.Sso
 | 
			
		||||
                                data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.PASSWORD } -> LoginMode.Password
 | 
			
		||||
                                else                                                                    -> LoginMode.Unsupported
 | 
			
		||||
                                data.supportedLoginTypes.contains(LoginFlowTypes.SSO)      -> LoginMode.Sso
 | 
			
		||||
                                data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
 | 
			
		||||
                                else                                                       -> LoginMode.Unsupported
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
 | 
			
		||||
@ -648,7 +684,7 @@ class LoginViewModel @AssistedInject constructor(
 | 
			
		||||
                                            asyncHomeServerLoginFlowRequest = Uninitialized,
 | 
			
		||||
                                            homeServerUrl = data.homeServerUrl,
 | 
			
		||||
                                            loginMode = loginMode,
 | 
			
		||||
                                            loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList()
 | 
			
		||||
                                            loginModeSupportedTypes = data.supportedLoginTypes.toList()
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,9 @@ import com.airbnb.mvrx.MvRxState
 | 
			
		||||
import com.airbnb.mvrx.PersistState
 | 
			
		||||
import com.airbnb.mvrx.Success
 | 
			
		||||
import com.airbnb.mvrx.Uninitialized
 | 
			
		||||
import im.vector.matrix.android.api.auth.SSO_REDIRECT_PATH
 | 
			
		||||
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
 | 
			
		||||
import im.vector.riotx.core.extensions.appendParamToUrl
 | 
			
		||||
 | 
			
		||||
data class LoginViewState(
 | 
			
		||||
        val asyncLoginAction: Async<Unit> = Uninitialized,
 | 
			
		||||
@ -64,4 +67,22 @@ data class LoginViewState(
 | 
			
		||||
    fun isUserLogged(): Boolean {
 | 
			
		||||
        return asyncLoginAction is Success
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getSsoUrl(): String {
 | 
			
		||||
        return buildString {
 | 
			
		||||
            append(homeServerUrl?.trim { it == '/' })
 | 
			
		||||
            append(SSO_REDIRECT_PATH)
 | 
			
		||||
            // Set a redirect url we will intercept later
 | 
			
		||||
            appendParamToUrl(SSO_REDIRECT_URL_PARAM, RIOTX_REDIRECT_URL)
 | 
			
		||||
            deviceId?.takeIf { it.isNotBlank() }?.let {
 | 
			
		||||
                // But https://github.com/matrix-org/synapse/issues/5755
 | 
			
		||||
                appendParamToUrl("device_id", it)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
 | 
			
		||||
        private const val RIOTX_REDIRECT_URL = "riotx://riotx"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,8 +33,6 @@ import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import im.vector.matrix.android.api.auth.LOGIN_FALLBACK_PATH
 | 
			
		||||
import im.vector.matrix.android.api.auth.REGISTER_FALLBACK_PATH
 | 
			
		||||
import im.vector.matrix.android.api.auth.SSO_FALLBACK_PATH
 | 
			
		||||
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.Credentials
 | 
			
		||||
import im.vector.matrix.android.internal.di.MoshiProvider
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
@ -48,7 +46,7 @@ import java.net.URLDecoder
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This screen is displayed for SSO login and also when the application does not support login flow or registration flow
 | 
			
		||||
 * This screen is displayed when the application does not support login flow or registration flow
 | 
			
		||||
 * of the homeserver, as a fallback to login or to create an account
 | 
			
		||||
 */
 | 
			
		||||
class LoginWebFragment @Inject constructor(
 | 
			
		||||
@ -128,17 +126,7 @@ class LoginWebFragment @Inject constructor(
 | 
			
		||||
        val url = buildString {
 | 
			
		||||
            append(state.homeServerUrl?.trim { it == '/' })
 | 
			
		||||
            if (state.signMode == SignMode.SignIn) {
 | 
			
		||||
                if (state.loginMode == LoginMode.Sso) {
 | 
			
		||||
                    append(SSO_FALLBACK_PATH)
 | 
			
		||||
                    // We do not want to deal with the result, so let the fallback login page to handle it for us
 | 
			
		||||
                    appendParamToUrl(SSO_REDIRECT_URL_PARAM,
 | 
			
		||||
                            buildString {
 | 
			
		||||
                                append(state.homeServerUrl?.trim { it == '/' })
 | 
			
		||||
                                append(LOGIN_FALLBACK_PATH)
 | 
			
		||||
                            })
 | 
			
		||||
                } else {
 | 
			
		||||
                    append(LOGIN_FALLBACK_PATH)
 | 
			
		||||
                }
 | 
			
		||||
                append(LOGIN_FALLBACK_PATH)
 | 
			
		||||
                state.deviceId?.takeIf { it.isNotBlank() }?.let {
 | 
			
		||||
                    // But https://github.com/matrix-org/synapse/issues/5755
 | 
			
		||||
                    appendParamToUrl("device_id", it)
 | 
			
		||||
@ -226,7 +214,9 @@ class LoginWebFragment @Inject constructor(
 | 
			
		||||
             * @return
 | 
			
		||||
             */
 | 
			
		||||
            override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
 | 
			
		||||
                if (null != url && url.startsWith("js:")) {
 | 
			
		||||
                if (url == null) return super.shouldOverrideUrlLoading(view, url as String?)
 | 
			
		||||
 | 
			
		||||
                if (url.startsWith("js:")) {
 | 
			
		||||
                    var json = url.substring(3)
 | 
			
		||||
                    var javascriptResponse: JavascriptResponse? = null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -22,10 +22,10 @@ import com.airbnb.mvrx.ViewModelContext
 | 
			
		||||
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.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.failure.toRegistrationFlowResponse
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
 | 
			
		||||
import im.vector.matrix.rx.rx
 | 
			
		||||
 | 
			
		||||
@ -30,13 +30,13 @@ 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.NoOpMatrixCallback
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.failure.Failure
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
 | 
			
		||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
 | 
			
		||||
 | 
			
		||||
@ -28,9 +28,9 @@ import com.squareup.inject.assisted.AssistedInject
 | 
			
		||||
import im.vector.matrix.android.api.MatrixCallback
 | 
			
		||||
import im.vector.matrix.android.api.auth.AuthenticationService
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
 | 
			
		||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.util.Cancelable
 | 
			
		||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
 | 
			
		||||
import im.vector.riotx.core.di.ActiveSessionHolder
 | 
			
		||||
import im.vector.riotx.core.extensions.hasUnsavedKeys
 | 
			
		||||
import im.vector.riotx.core.platform.VectorViewModel
 | 
			
		||||
@ -105,9 +105,9 @@ class SoftLogoutViewModel @AssistedInject constructor(
 | 
			
		||||
                    is LoginFlowResult.Success            -> {
 | 
			
		||||
                        val loginMode = when {
 | 
			
		||||
                            // SSO login is taken first
 | 
			
		||||
                            data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.SSO }      -> LoginMode.Sso
 | 
			
		||||
                            data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.PASSWORD } -> LoginMode.Password
 | 
			
		||||
                            else                                                                    -> LoginMode.Unsupported
 | 
			
		||||
                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)      -> LoginMode.Sso
 | 
			
		||||
                            data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
 | 
			
		||||
                            else                                                       -> LoginMode.Unsupported
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								vector/src/main/res/drawable/ic_back_24dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vector/src/main/res/drawable/ic_back_24dp.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="24"
 | 
			
		||||
    android:viewportHeight="24">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M20,12H4"
 | 
			
		||||
        android:strokeWidth="2"
 | 
			
		||||
        android:strokeColor="#2E2F32"
 | 
			
		||||
        android:strokeLineCap="round"
 | 
			
		||||
        android:strokeLineJoin="round"
 | 
			
		||||
        tools:strokeColor="#00F000" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M10,18L4,12L10,6"
 | 
			
		||||
        android:strokeWidth="2"
 | 
			
		||||
        android:strokeColor="#2E2F32"
 | 
			
		||||
        android:strokeLineCap="round"
 | 
			
		||||
        android:strokeLineJoin="round"
 | 
			
		||||
        tools:strokeColor="#00F000" />
 | 
			
		||||
</vector>
 | 
			
		||||
@ -2,8 +2,10 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Light.v21" parent="AppTheme.Base.Light">
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <!-- Use dark color, to have enough contrast with icons color. windowLightStatusBar is only available in API 23+ -->
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_dark</item>
 | 
			
		||||
        <!-- Use dark color, to have enough contrast with icons color. windowLightNavigationBar is only available in API 27+ -->
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_dark</item>
 | 
			
		||||
 | 
			
		||||
        <!-- enable window content transitions -->
 | 
			
		||||
        <item name="android:windowContentTransitions">true</item>
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,10 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Status.v21" parent="AppTheme.Base.Status">
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <!-- Use dark color, to have enough contrast with icons color. windowLightStatusBar is only available in API 23+ -->
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_dark</item>
 | 
			
		||||
        <!-- Use dark color, to have enough contrast with icons color. windowLightNavigationBar is only available in API 27+ -->
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_dark</item>
 | 
			
		||||
 | 
			
		||||
        <!-- enable window content transitions -->
 | 
			
		||||
        <item name="android:windowContentTransitions">true</item>
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Light.v23" parent="AppTheme.Light.v21">
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:windowLightStatusBar">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,8 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Status.v23" parent="AppTheme.Status.v21">
 | 
			
		||||
        <item name="android:windowLightStatusBar">false</item>
 | 
			
		||||
        <item name="android:statusBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:windowLightStatusBar">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Status" parent="AppTheme.Status.v23"/>
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Light.v27" parent="AppTheme.Light.v23">
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:windowLightNavigationBar">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <style name="AppTheme.Status.v27" parent="AppTheme.Status.v23">
 | 
			
		||||
        <item name="android:navigationBarColor">@color/riotx_header_panel_background_light</item>
 | 
			
		||||
        <item name="android:windowLightNavigationBar">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user