Refine sign in flow to match designs and iOS flow (#100)

* Fix dark theme

* First attempt at replicating iOS' UI & flows.

* Try to fix Maestro tests

* Add error dialogs and tests

* Remove unused modifiers

* Try to fix detekt issues

* Tidy up maestro login flow a bit

* Add `CompoundColorPalette` with some needed colors

* Fixes to designs

* Fix detekt issues

* More design fixes

* Some other minor design fixes

* Add changelog

* Minor tweaks.

* Remove legacy dark material theme as it's no longer needed.

* Move sliding sync 'learn more' url to constants object

* Remove unused focusManager

* Change how the displayed homeserver works

* Keep user input as homeserver if it's valid

* Remove `CompoundColorPalette`, try to fix issue when toggling dark mode.

* Add `@Stable` to the theme, adjust how it toggles in dark mode

* Remove unused strings

* Update screenshots

* Re-organize components in LoginRootScreen

* Bump min coverage to 55, max to 60

* Always replace the snapshots contents when running `recordPaparazzi`

* Fix dark theme preview of components using content colors

* Add `BackButton` component

* Handle errors with dialogs in a generic way

* Align our Dialog components with the designs, use them were needed

* Use a `MatrixHomeserverDetails` data class instead of just an URL.

* `AuthenticationService.getHomeserverDetails()` now returns a `StateFlow`.

Also, try to fix coverage issues in tests.
This commit is contained in:
Jorge Martin Espinosa 2023-03-06 09:30:16 +01:00 committed by GitHub
parent ced183dd80
commit 33b88b8026
271 changed files with 1431 additions and 871 deletions

View file

@ -18,6 +18,7 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-library")
id("kotlin-parcelize")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.8.10"
}

View file

@ -19,13 +19,13 @@ package io.element.android.libraries.matrix.api.auth
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface MatrixAuthenticationService {
fun isLoggedIn(): Flow<Boolean>
suspend fun getLatestSessionId(): SessionId?
suspend fun restoreSession(sessionId: SessionId): MatrixClient?
fun getHomeserver(): String?
fun getHomeserverOrDefault(): String
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
suspend fun setHomeserver(homeserver: String)
suspend fun login(username: String, password: String): SessionId
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.auth
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
@Parcelize
data class MatrixHomeServerDetails(
val url: String,
val supportsPasswordLogin: Boolean,
val authenticationIssuer: String?
): Parcelable {
constructor(homeserverLoginDetails: HomeserverLoginDetails) : this(
homeserverLoginDetails.url(),
homeserverLoginDetails.supportsPasswordLogin(),
homeserverLoginDetails.authenticationIssuer()
)
}

View file

@ -19,8 +19,10 @@ package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.impl.RustMatrixClient
@ -29,6 +31,8 @@ import io.element.android.libraries.matrix.session.SessionData
import io.element.android.libraries.sessionstorage.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.Client
@ -39,6 +43,7 @@ import java.io.File
import javax.inject.Inject
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class RustMatrixAuthenticationService @Inject constructor(
private val baseDirectory: File,
private val coroutineScope: CoroutineScope,
@ -47,6 +52,8 @@ class RustMatrixAuthenticationService @Inject constructor(
private val authService: AuthenticationService,
) : MatrixAuthenticationService {
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
override fun isLoggedIn(): Flow<Boolean> {
return sessionStore.isLoggedIn()
}
@ -74,13 +81,15 @@ class RustMatrixAuthenticationService @Inject constructor(
}
}
override fun getHomeserver(): String? = authService.homeserverDetails()?.url()
override fun getHomeserverOrDefault(): String = getHomeserver() ?: "matrix.org"
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
override suspend fun setHomeserver(homeserver: String) {
withContext(coroutineDispatchers.io) {
authService.configureHomeserver(homeserver)
val homeServerDetails = authService.homeserverDetails()?.use { MatrixHomeServerDetails(it) }
if (homeServerDetails != null) {
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
}
}
}

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
@ -34,8 +35,10 @@ const val A_MESSAGE = "Hello world!"
const val A_REPLY = "OK, I'll be there!"
const val ANOTHER_MESSAGE = "Hello universe!"
const val A_HOMESERVER = "matrix.org"
const val A_HOMESERVER_2 = "matrix-client.org"
const val A_HOMESERVER_URL = "matrix.org"
const val A_HOMESERVER_URL_2 = "matrix-client.org"
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, true, null)
const val AN_AVATAR_URL = "mxc://data"

View file

@ -18,46 +18,47 @@ package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_USER_ID
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
class FakeAuthenticationService : MatrixAuthenticationService {
private var homeserver: String = A_HOMESERVER
private var homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private var loginError: Throwable? = null
private var changeServerError: Throwable? = null
override fun isLoggedIn(): Flow<Boolean> {
return flowOf(false)
}
override suspend fun getLatestSessionId(): UserId? {
override suspend fun getLatestSessionId(): SessionId? {
return null
}
override suspend fun restoreSession(userId: UserId): MatrixClient? {
override suspend fun restoreSession(sessionId: SessionId): MatrixClient? {
return null
}
override fun getHomeserver(): String? {
return null
}
fun givenHomeserver(homeserver: String) {
this.homeserver = homeserver
}
override fun getHomeserverOrDefault(): String {
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> {
return homeserver
}
fun givenHomeserver(homeserver: MatrixHomeServerDetails) {
this.homeserver.value = homeserver
}
override suspend fun setHomeserver(homeserver: String) {
changeServerError?.let { throw it }
delay(100)
}
override suspend fun login(username: String, password: String): UserId {
override suspend fun login(username: String, password: String): SessionId {
delay(100)
loginError?.let { throw it }
return A_USER_ID
@ -66,4 +67,8 @@ class FakeAuthenticationService : MatrixAuthenticationService {
fun givenLoginError(throwable: Throwable?) {
loginError = throwable
}
fun givenChangeServerError(throwable: Throwable?) {
changeServerError = throwable
}
}