Skip to content

Commit

Permalink
Merge pull request #7338 from vector-im/feature/ons/qr_code_login_ui
Browse files Browse the repository at this point in the history
QR Code Login UI
  • Loading branch information
onurays authored Oct 17, 2022
2 parents b6639fc + d3a24fe commit 47c8714
Show file tree
Hide file tree
Showing 50 changed files with 1,693 additions and 12 deletions.
1 change: 1 addition & 0 deletions changelog.d/7338.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement QR Code Login UI
37 changes: 37 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2183,6 +2183,7 @@
<string name="login_signin_matrix_id_password_notice">If you don’t know your password, go back to reset it.</string>
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
<string name="login_scan_qr_code">Scan QR code</string>

<string name="seen_by">Seen by</string>

Expand Down Expand Up @@ -3330,6 +3331,9 @@
<string name="device_manager_session_rename_edit_hint">Session name</string>
<string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string>
<string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string>
<string name="device_manager_sessions_sign_in_with_qr_code_title">Sign in with QR Code</string>
<string name="device_manager_sessions_sign_in_with_qr_code_description">You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:</string>

<string name="device_manager_learn_more_sessions_inactive_title">Inactive sessions</string>
<string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.</string>
<string name="device_manager_learn_more_sessions_unverified_title">Unverified sessions</string>
Expand Down Expand Up @@ -3364,6 +3368,39 @@
<string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
<string name="onboarding_new_app_layout_button_try">Try it out</string>

<string name="one">1</string>
<string name="two">2</string>
<string name="three">3</string>

<!-- QR Code Login -->
<string name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
<string name="qr_code_login_header_scan_qr_code_description">Use the camera on this device to scan the QR code shown on your other device:</string>
<string name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
<string name="qr_code_login_header_show_qr_code_new_device_description">Use your signed in device to scan the QR code below:</string>
<string name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device that’s signed out.</string>
<string name="qr_code_login_header_connected_title">Secure connection established</string>
<string name="qr_code_login_header_connected_description">Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:</string>
<string name="qr_code_login_header_failed_title">Unsuccessful connection</string>
<string name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
<string name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
<string name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
<string name="qr_code_login_new_device_instruction_1">Open ${app_name} on your other device</string>
<string name="qr_code_login_new_device_instruction_2">Go to Settings -> Security &amp; Privacy -> Show All Sessions</string>
<string name="qr_code_login_new_device_instruction_3">Select \'Show QR code in this device\'</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
<string name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
<string name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
<string name="qr_code_login_scan_qr_code_button">Scan QR code</string>
<string name="qr_code_login_connecting_to_device">Connecting to device</string>
<string name="qr_code_login_signing_in">Signing you in</string>
<string name="qr_code_login_status_no_match">No match?</string>
<string name="qr_code_login_try_again">Try again</string>
<string name="qr_code_login_confirm_security_code">Confirm</string>
<string name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>

<!-- WYSIWYG Composer -->
<string name="rich_text_editor_format_bold">Apply bold format</string>
<string name="rich_text_editor_format_italic">Apply italic format</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="QrCodeLoginInstructionsView">
<attr name="qrCodeLoginInstruction1" format="string" />
<attr name="qrCodeLoginInstruction2" format="string" />
<attr name="qrCodeLoginInstruction3" format="string" />
<attr name="qrCodeLoginInstruction4" format="string" />
</declare-styleable>

</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="QrCodeLoginHeaderView">
<attr name="qrCodeLoginHeaderTitle" format="string" />
<attr name="qrCodeLoginHeaderDescription" format="string" />
<attr name="qrCodeLoginHeaderImageResource" format="reference" />
<attr name="qrCodeLoginHeaderImageBackgroundTint" format="color" />
</declare-styleable>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ interface AuthenticationService {
deviceId: String? = null
): Session

/**
* @param homeServerConnectionConfig the information about the homeserver and other configuration
* Return true if qr code login is supported by the server, false otherwise.
*/
suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean

/**
* Authenticate using m.login.token method during sign in with QR code.
* @param homeServerConnectionConfig the information about the homeserver and other configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ data class HomeServerCapabilities(
/**
* True if the home server supports controlling the logout of all devices when changing password.
*/
val canControlLogoutDevices: Boolean = false
val canControlLogoutDevices: Boolean = false,

/**
* True if the home server supports login via qr code, false otherwise.
*/
val canLoginWithQrCode: Boolean = false,
) {

enum class RoomCapabilitySupport {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session
Expand All @@ -43,6 +44,7 @@ import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask
import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk
import org.matrix.android.sdk.internal.di.Unauthenticated
Expand Down Expand Up @@ -406,6 +408,20 @@ internal class DefaultAuthenticationService @Inject constructor(
)
}

override suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val versions = runCatching {
executeRequest(null) {
authAPI.versions()
}
}
return if (versions.isSuccess) {
versions.getOrNull()?.doesServerSupportQrCodeLogin().orFalse()
} else {
false
}
}

override suspend fun loginUsingQrLoginToken(
homeServerConnectionConfig: HomeServerConnectionConfig,
loginToken: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"

/**
* Return true if the SDK supports this homeserver version.
Expand All @@ -78,6 +79,10 @@ internal fun Versions.doesServerSupportThreads(): Boolean {
return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
}

internal fun Versions.doesServerSupportQrCodeLogin(): Boolean {
return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false
}

/**
* Return true if the server support the lazy loading of room members.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
Expand All @@ -63,7 +64,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 38L,
schemaVersion = 39L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
Expand Down Expand Up @@ -111,5 +112,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
if (oldVersion < 38) MigrateSessionTo038(realm).perform()
if (oldVersion < 39) MigrateSessionTo039(realm).perform()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ internal object HomeServerCapabilitiesMapper {
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
roomVersions = mapRoomVersion(entity.roomVersionsJson),
canUseThreading = entity.canUseThreading,
canControlLogoutDevices = entity.canControlLogoutDevices
canControlLogoutDevices = entity.canControlLogoutDevices,
canLoginWithQrCode = entity.canLoginWithQrCode,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.migration

import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator

internal class MigrateSessionTo039(realm: DynamicRealm) : RealmMigrator(realm, 39) {

override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, Boolean::class.java)
?.transform { obj ->
obj.set(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, false)
}
?.forceRefreshOfHomeServerCapabilities()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ internal open class HomeServerCapabilitiesEntity(
var defaultIdentityServerUrl: String? = null,
var lastUpdatedTimestamp: Long = 0L,
var canUseThreading: Boolean = false,
var canControlLogoutDevices: Boolean = false
var canControlLogoutDevices: Boolean = false,
var canLoginWithQrCode: Boolean = false,
) : RealmObject() {

companion object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
Expand Down Expand Up @@ -132,8 +132,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
}
homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
getVersionResult?.doesServerSupportThreads().orFalse()
}

if (getMediaConfigResult != null) {
Expand All @@ -144,6 +142,9 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getVersionResult != null) {
homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk()
homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices()
homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
getVersionResult.doesServerSupportThreads()
homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin()
}

if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.newAppLayoutEnabled,
factory = VectorFeatures::isNewAppLayoutFeatureEnabled
),
createBooleanFeature(
label = "Enable QR Code Login",
key = DebugFeatureKeys.qrCodeLoginEnabled,
factory = VectorFeatures::isQrCodeLoginEnabled
),
createBooleanFeature(
label = "Allow QR Code Login for all servers",
key = DebugFeatureKeys.qrCodeLoginForAllServers,
factory = VectorFeatures::isQrCodeLoginForAllServers
),
createBooleanFeature(
label = "Show QR Code Login in Device Manager",
key = DebugFeatureKeys.reciprocateQrCodeLogin,
factory = VectorFeatures::isReciprocateQrCodeLogin
),
createBooleanFeature(
label = "Enable Voice Broadcast",
key = DebugFeatureKeys.voiceBroadcastEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ class DebugVectorFeatures(
override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
?: vectorFeatures.isNewAppLayoutFeatureEnabled()

override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled)
?: vectorFeatures.isQrCodeLoginEnabled()

override fun isQrCodeLoginForAllServers() = read(DebugFeatureKeys.qrCodeLoginForAllServers)
?: vectorFeatures.isQrCodeLoginForAllServers()

override fun isReciprocateQrCodeLogin() = read(DebugFeatureKeys.reciprocateQrCodeLogin)
?: vectorFeatures.isReciprocateQrCodeLogin()

override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
?: vectorFeatures.isVoiceBroadcastEnabled()

Expand Down Expand Up @@ -138,5 +147,8 @@ object DebugFeatureKeys {
val screenSharing = booleanPreferencesKey("screen-sharing")
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled")
val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
}
1 change: 1 addition & 0 deletions vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity" />
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
<activity android:name=".features.login.qr.QrCodeLoginActivity" />

<!-- Services -->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import im.vector.app.features.location.LocationSharingViewModel
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
import im.vector.app.features.location.preview.LocationPreviewViewModel
import im.vector.app.features.login.LoginViewModel
import im.vector.app.features.login.qr.QrCodeLoginViewModel
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
import im.vector.app.features.media.VectorAttachmentViewerViewModel
import im.vector.app.features.onboarding.OnboardingViewModel
Expand Down Expand Up @@ -662,6 +663,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(RenameSessionViewModel::class)
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>

@Binds
@IntoMap
@MavericksViewModelKey(QrCodeLoginViewModel::class)
fun qrCodeLoginViewModelFactory(factory: QrCodeLoginViewModel.Factory): MavericksAssistedViewModelFactory<*, *>

@Binds
@IntoMap
@MavericksViewModelKey(SessionLearnMoreViewModel::class)
Expand Down
Loading

0 comments on commit 47c8714

Please sign in to comment.