mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #7338 from vector-im/feature/ons/qr_code_login_ui
QR Code Login UI
This commit is contained in:
commit
47c87141b2
1
changelog.d/7338.wip
Normal file
1
changelog.d/7338.wip
Normal file
@ -0,0 +1 @@
|
||||
Implement QR Code Login UI
|
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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 & 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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
@ -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
|
||||
@ -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,
|
||||
|
@ -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.
|
||||
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
@ -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.
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
@ -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) {
|
||||
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
@ -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 -->
|
||||
|
||||
|
@ -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
|
||||
@ -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)
|
||||
|
@ -40,6 +40,9 @@ interface VectorFeatures {
|
||||
* use [VectorPreferences.isNewAppLayoutEnabled] instead.
|
||||
*/
|
||||
fun isNewAppLayoutFeatureEnabled(): Boolean
|
||||
fun isQrCodeLoginEnabled(): Boolean
|
||||
fun isQrCodeLoginForAllServers(): Boolean
|
||||
fun isReciprocateQrCodeLogin(): Boolean
|
||||
fun isVoiceBroadcastEnabled(): Boolean
|
||||
}
|
||||
|
||||
@ -56,5 +59,8 @@ class DefaultVectorFeatures : VectorFeatures {
|
||||
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
|
||||
override fun forceUsageOfOpusEncoder(): Boolean = false
|
||||
override fun isNewAppLayoutFeatureEnabled(): Boolean = true
|
||||
override fun isQrCodeLoginEnabled(): Boolean = true
|
||||
override fun isQrCodeLoginForAllServers(): Boolean = false
|
||||
override fun isReciprocateQrCodeLogin(): Boolean = false
|
||||
override fun isVoiceBroadcastEnabled(): Boolean = false
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class QrCodeLoginAction : VectorViewModelAction {
|
||||
data class OnQrCodeScanned(val qrCode: String) : QrCodeLoginAction()
|
||||
object GenerateQrCode : QrCodeLoginAction()
|
||||
object ShowQrCode : QrCodeLoginAction()
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.lib.core.utils.compat.getParcelableCompat
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginActivity : SimpleFragmentActivity() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by viewModel()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
views.toolbar.visibility = View.GONE
|
||||
|
||||
val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG)
|
||||
if (isFirstCreation()) {
|
||||
when (qrCodeLoginArgs?.loginType) {
|
||||
QrCodeLoginType.LOGIN -> {
|
||||
showInstructionsFragment(qrCodeLoginArgs)
|
||||
}
|
||||
QrCodeLoginType.LINK_A_DEVICE -> {
|
||||
if (qrCodeLoginArgs.showQrCodeImmediately) {
|
||||
handleNavigateToShowQrCodeScreen()
|
||||
} else {
|
||||
showInstructionsFragment(qrCodeLoginArgs)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
Timber.i("QrCodeLoginArgs is null. This is not expected.")
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
private fun showInstructionsFragment(qrCodeLoginArgs: QrCodeLoginArgs) {
|
||||
addFragment(
|
||||
views.container,
|
||||
QrCodeLoginInstructionsFragment::class.java,
|
||||
qrCodeLoginArgs,
|
||||
tag = FRAGMENT_QR_CODE_INSTRUCTIONS_TAG
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen()
|
||||
QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNavigateToShowQrCodeScreen() {
|
||||
addFragment(
|
||||
views.container,
|
||||
QrCodeLoginShowQrCodeFragment::class.java,
|
||||
tag = FRAGMENT_SHOW_QR_CODE_TAG
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNavigateToStatusScreen() {
|
||||
addFragment(
|
||||
views.container,
|
||||
QrCodeLoginStatusFragment::class.java,
|
||||
tag = FRAGMENT_QR_CODE_STATUS_TAG
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val FRAGMENT_QR_CODE_INSTRUCTIONS_TAG = "FRAGMENT_QR_CODE_INSTRUCTIONS_TAG"
|
||||
private const val FRAGMENT_SHOW_QR_CODE_TAG = "FRAGMENT_SHOW_QR_CODE_TAG"
|
||||
private const val FRAGMENT_QR_CODE_STATUS_TAG = "FRAGMENT_QR_CODE_STATUS_TAG"
|
||||
|
||||
fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent {
|
||||
return Intent(context, QrCodeLoginActivity::class.java).apply {
|
||||
putExtra(Mavericks.KEY_ARG, qrCodeLoginArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class QrCodeLoginArgs(
|
||||
val loginType: QrCodeLoginType,
|
||||
val showQrCodeImmediately: Boolean,
|
||||
) : Parcelable
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
sealed class QrCodeLoginConnectionStatus {
|
||||
object ConnectingToDevice : QrCodeLoginConnectionStatus()
|
||||
data class Connected(val securityCode: String, val canConfirmSecurityCode: Boolean) : QrCodeLoginConnectionStatus()
|
||||
object SigningIn : QrCodeLoginConnectionStatus()
|
||||
data class Failed(val errorType: QrCodeLoginErrorType, val canTryAgain: Boolean) : QrCodeLoginConnectionStatus()
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
enum class QrCodeLoginErrorType {
|
||||
DEVICE_IS_NOT_SUPPORTED,
|
||||
TIMEOUT,
|
||||
REQUEST_WAS_DENIED,
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.ViewQrCodeLoginHeaderBinding
|
||||
|
||||
class QrCodeLoginHeaderView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewQrCodeLoginHeaderBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.QrCodeLoginHeaderView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setTitle(it)
|
||||
setDescription(it)
|
||||
setImage(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTitle(typedArray: TypedArray) {
|
||||
val title = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderTitle)
|
||||
setTitle(title)
|
||||
}
|
||||
|
||||
private fun setDescription(typedArray: TypedArray) {
|
||||
val description = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderDescription)
|
||||
setDescription(description)
|
||||
}
|
||||
|
||||
private fun setImage(typedArray: TypedArray) {
|
||||
val imageResource = typedArray.getResourceId(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageResource, 0)
|
||||
val backgroundTint = typedArray.getColor(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageBackgroundTint, 0)
|
||||
setImage(imageResource, backgroundTint)
|
||||
}
|
||||
|
||||
fun setTitle(title: String?) {
|
||||
binding.qrCodeLoginHeaderTitleTextView.setTextOrHide(title)
|
||||
}
|
||||
|
||||
fun setDescription(description: String?) {
|
||||
binding.qrCodeLoginHeaderDescriptionTextView.setTextOrHide(description)
|
||||
}
|
||||
|
||||
fun setImage(imageResource: Int, backgroundTintColor: Int) {
|
||||
binding.qrCodeLoginHeaderImageView.setImageResource(imageResource)
|
||||
binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding
|
||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginInstructionsFragment : VectorBaseFragment<FragmentQrCodeLoginInstructionsBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginInstructionsBinding {
|
||||
return FragmentQrCodeLoginInstructionsBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initScanQrCodeButton()
|
||||
initShowQrCodeButton()
|
||||
}
|
||||
|
||||
private fun initShowQrCodeButton() {
|
||||
views.qrCodeLoginInstructionsShowQrCodeButton.debouncedClicks {
|
||||
viewModel.handle(QrCodeLoginAction.ShowQrCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initScanQrCodeButton() {
|
||||
views.qrCodeLoginInstructionsScanQrCodeButton.debouncedClicks {
|
||||
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
|
||||
}
|
||||
}
|
||||
|
||||
private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
|
||||
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
|
||||
|
||||
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
|
||||
onQrCodeScanned(scannedQrCode)
|
||||
} else {
|
||||
onQrCodeScannerFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onQrCodeScanned(scannedQrCode: String) {
|
||||
viewModel.handle(QrCodeLoginAction.OnQrCodeScanned(scannedQrCode))
|
||||
}
|
||||
|
||||
private fun onQrCodeScannerFailed() {
|
||||
Timber.d("QrCodeLoginInstructionsFragment.onQrCodeScannerFailed")
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
if (state.loginType == QrCodeLoginType.LOGIN) {
|
||||
views.qrCodeLoginInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(R.string.qr_code_login_new_device_instruction_1),
|
||||
getString(R.string.qr_code_login_new_device_instruction_2),
|
||||
getString(R.string.qr_code_login_new_device_instruction_3),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
views.qrCodeLoginInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(R.string.qr_code_login_link_a_device_scan_qr_code_instruction_1),
|
||||
getString(R.string.qr_code_login_link_a_device_scan_qr_code_instruction_2),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ViewQrCodeLoginInstructionsBinding
|
||||
|
||||
class QrCodeLoginInstructionsView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewQrCodeLoginInstructionsBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.QrCodeLoginInstructionsView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setInstructions(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInstructions(typedArray: TypedArray) {
|
||||
val instruction1 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction1)
|
||||
val instruction2 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction2)
|
||||
val instruction3 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction3)
|
||||
setInstructions(
|
||||
listOf(
|
||||
instruction1,
|
||||
instruction2,
|
||||
instruction3,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun setInstructions(instructions: List<String?>?) {
|
||||
setInstruction(binding.instructions1Layout, binding.instruction1TextView, instructions?.getOrNull(0))
|
||||
setInstruction(binding.instructions2Layout, binding.instruction2TextView, instructions?.getOrNull(1))
|
||||
setInstruction(binding.instructions3Layout, binding.instruction3TextView, instructions?.getOrNull(2))
|
||||
}
|
||||
|
||||
private fun setInstruction(instructionLayout: LinearLayout, instructionTextView: TextView, instruction: String?) {
|
||||
instruction?.let {
|
||||
instructionLayout.isVisible = true
|
||||
instructionTextView.text = instruction
|
||||
} ?: run {
|
||||
instructionLayout.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginShowQrCodeBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginShowQrCodeFragment : VectorBaseFragment<FragmentQrCodeLoginShowQrCodeBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginShowQrCodeBinding {
|
||||
return FragmentQrCodeLoginShowQrCodeBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initCancelButton()
|
||||
viewModel.handle(QrCodeLoginAction.GenerateQrCode)
|
||||
}
|
||||
|
||||
private fun initCancelButton() {
|
||||
views.qrCodeLoginShowQrCodeCancelButton.debouncedClicks {
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInstructions(loginType: QrCodeLoginType) {
|
||||
if (loginType == QrCodeLoginType.LOGIN) {
|
||||
views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(R.string.qr_code_login_header_show_qr_code_new_device_description))
|
||||
views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(R.string.qr_code_login_new_device_instruction_1),
|
||||
getString(R.string.qr_code_login_new_device_instruction_2),
|
||||
getString(R.string.qr_code_login_new_device_instruction_3),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(R.string.qr_code_login_header_show_qr_code_link_a_device_description))
|
||||
views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(R.string.qr_code_login_link_a_device_show_qr_code_instruction_1),
|
||||
getString(R.string.qr_code_login_link_a_device_show_qr_code_instruction_2),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showQrCode(qrCodeData: String) {
|
||||
views.qrCodeLoginSHowQrCodeImageView.setData(qrCodeData)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
state.generatedQrCodeData?.let { qrCodeData ->
|
||||
showQrCode(qrCodeData)
|
||||
}
|
||||
setInstructions(state.loginType)
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginStatusFragment : VectorBaseFragment<FragmentQrCodeLoginStatusBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginStatusBinding {
|
||||
return FragmentQrCodeLoginStatusBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initCancelButton()
|
||||
}
|
||||
|
||||
private fun initCancelButton() {
|
||||
views.qrCodeLoginStatusCancelButton.debouncedClicks {
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFailed(connectionStatus: QrCodeLoginConnectionStatus.Failed) {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = false
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = true
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = connectionStatus.canTryAgain
|
||||
views.qrCodeLoginStatusHeaderView.setTitle(getString(R.string.qr_code_login_header_failed_title))
|
||||
views.qrCodeLoginStatusHeaderView.setDescription(getErrorCode(connectionStatus.errorType))
|
||||
views.qrCodeLoginStatusHeaderView.setImage(
|
||||
imageResource = R.drawable.ic_qr_code_login_failed,
|
||||
backgroundTintColor = ThemeUtils.getColor(requireContext(), R.attr.colorError)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getErrorCode(errorType: QrCodeLoginErrorType): String {
|
||||
return when (errorType) {
|
||||
QrCodeLoginErrorType.DEVICE_IS_NOT_SUPPORTED -> getString(R.string.qr_code_login_header_failed_device_is_not_supported_description)
|
||||
QrCodeLoginErrorType.TIMEOUT -> getString(R.string.qr_code_login_header_failed_timeout_description)
|
||||
QrCodeLoginErrorType.REQUEST_WAS_DENIED -> getString(R.string.qr_code_login_header_failed_denied_description)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnectingToDevice() {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = true
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = false
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_connecting_to_device)
|
||||
}
|
||||
|
||||
private fun handleSigningIn() {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = true
|
||||
views.qrCodeLoginStatusHeaderView.apply {
|
||||
isVisible = true
|
||||
setTitle(getString(R.string.dialog_title_success))
|
||||
setDescription("")
|
||||
setImage(R.drawable.ic_tick, ThemeUtils.getColor(requireContext(), R.attr.colorPrimary))
|
||||
}
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = false
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_signing_in)
|
||||
}
|
||||
|
||||
private fun handleConnectionEstablished(connectionStatus: QrCodeLoginConnectionStatus.Connected, loginType: QrCodeLoginType) {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = loginType == QrCodeLoginType.LINK_A_DEVICE
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = false
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = true
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = true
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = loginType == QrCodeLoginType.LOGIN
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusSecurityCode.text = connectionStatus.securityCode
|
||||
views.qrCodeLoginStatusHeaderView.setTitle(getString(R.string.qr_code_login_header_connected_title))
|
||||
views.qrCodeLoginStatusHeaderView.setDescription(getString(R.string.qr_code_login_header_connected_description))
|
||||
views.qrCodeLoginStatusHeaderView.setImage(
|
||||
imageResource = R.drawable.ic_qr_code_login_connected,
|
||||
backgroundTintColor = ThemeUtils.getColor(requireContext(), R.attr.colorPrimary)
|
||||
)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
when (state.connectionStatus) {
|
||||
is QrCodeLoginConnectionStatus.Connected -> handleConnectionEstablished(state.connectionStatus, state.loginType)
|
||||
QrCodeLoginConnectionStatus.ConnectingToDevice -> handleConnectingToDevice()
|
||||
QrCodeLoginConnectionStatus.SigningIn -> handleSigningIn()
|
||||
is QrCodeLoginConnectionStatus.Failed -> handleFailed(state.connectionStatus)
|
||||
null -> { /* NOOP */ }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
enum class QrCodeLoginType {
|
||||
LOGIN,
|
||||
LINK_A_DEVICE,
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class QrCodeLoginViewEvents : VectorViewEvents {
|
||||
object NavigateToStatusScreen : QrCodeLoginViewEvents()
|
||||
object NavigateToShowQrCodeScreen : QrCodeLoginViewEvents()
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: QrCodeLoginViewState,
|
||||
) : VectorViewModel<QrCodeLoginViewState, QrCodeLoginAction, QrCodeLoginViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> {
|
||||
override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
override fun handle(action: QrCodeLoginAction) {
|
||||
when (action) {
|
||||
is QrCodeLoginAction.OnQrCodeScanned -> handleOnQrCodeScanned(action)
|
||||
QrCodeLoginAction.GenerateQrCode -> handleQrCodeViewStarted()
|
||||
QrCodeLoginAction.ShowQrCode -> handleShowQrCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShowQrCode() {
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToShowQrCodeScreen)
|
||||
}
|
||||
|
||||
private fun handleQrCodeViewStarted() {
|
||||
val qrCodeData = generateQrCodeData()
|
||||
setState {
|
||||
copy(
|
||||
generatedQrCodeData = qrCodeData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) {
|
||||
if (isValidQrCode(action.qrCode)) {
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice
|
||||
)
|
||||
}
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFailed(errorType: QrCodeLoginErrorType, canTryAgain: Boolean) {
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.Failed(errorType, canTryAgain)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConnectionEstablished(securityCode: String) {
|
||||
val canConfirmSecurityCode = initialState.loginType == QrCodeLoginType.LINK_A_DEVICE
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.Connected(securityCode, canConfirmSecurityCode)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSigningIn() {
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.SigningIn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO. Implement in the logic related PR.
|
||||
private fun isValidQrCode(qrCode: String): Boolean {
|
||||
Timber.d("isValidQrCode: $qrCode")
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO. Implement in the logic related PR.
|
||||
private fun generateQrCodeData(): String {
|
||||
return "TODO"
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.login.qr
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
|
||||
data class QrCodeLoginViewState(
|
||||
val loginType: QrCodeLoginType,
|
||||
val connectionStatus: QrCodeLoginConnectionStatus? = null,
|
||||
val generatedQrCodeData: String? = null,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: QrCodeLoginArgs) : this(
|
||||
loginType = args.loginType,
|
||||
)
|
||||
}
|
@ -70,6 +70,8 @@ import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
|
||||
import im.vector.app.features.location.live.map.LiveLocationMapViewArgs
|
||||
import im.vector.app.features.login.LoginActivity
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.login.qr.QrCodeLoginActivity
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
@ -604,6 +606,14 @@ class DefaultNavigator @Inject constructor(
|
||||
activityResultLauncher.launch(screenCaptureIntent)
|
||||
}
|
||||
|
||||
override fun openLoginWithQrCode(context: Context, qrCodeLoginArgs: QrCodeLoginArgs) {
|
||||
QrCodeLoginActivity
|
||||
.getIntent(context, qrCodeLoginArgs)
|
||||
.also {
|
||||
context.startActivity(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Intent.start(context: Context) {
|
||||
context.startActivity(this)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationSharingMode
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
import im.vector.app.features.pin.PinMode
|
||||
@ -201,4 +202,9 @@ interface Navigator {
|
||||
screenCaptureIntent: Intent,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
)
|
||||
|
||||
fun openLoginWithQrCode(
|
||||
context: Context,
|
||||
qrCodeLoginArgs: QrCodeLoginArgs,
|
||||
)
|
||||
}
|
||||
|
@ -118,6 +118,35 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkQrCodeLoginCapability(homeServerUrl: String) {
|
||||
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = false
|
||||
)
|
||||
}
|
||||
} else if (vectorFeatures.isQrCodeLoginForAllServers()) {
|
||||
// allow for all servers
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
// check if selected server supports MSC3882 first
|
||||
homeServerConnectionConfigFactory.create(homeServerUrl)?.let {
|
||||
val canLoginWithQrCode = authenticationService.isQrLoginSupported(it)
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = canLoginWithQrCode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||
private val defaultHomeserverUrl = matrixOrgUrl
|
||||
|
||||
@ -680,6 +709,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
||||
checkQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,9 @@ data class OnboardingViewState(
|
||||
val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(),
|
||||
|
||||
@PersistState
|
||||
val personalizationState: PersonalizationState = PersonalizationState()
|
||||
val personalizationState: PersonalizationState = PersonalizationState(),
|
||||
|
||||
val canLoginWithQrCode: Boolean = false,
|
||||
) : MavericksState
|
||||
|
||||
enum class OnboardingFlow {
|
||||
|
@ -36,10 +36,13 @@ import im.vector.app.core.extensions.setOnFocusLostListener
|
||||
import im.vector.app.core.extensions.setOnImeDoneListener
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentFtueCombinedLoginBinding
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||
import im.vector.app.features.login.SocialLoginButtonsView
|
||||
import im.vector.app.features.login.SsoState
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.login.qr.QrCodeLoginType
|
||||
import im.vector.app.features.login.render
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
@ -55,6 +58,7 @@ class FtueAuthCombinedLoginFragment :
|
||||
|
||||
@Inject lateinit var loginFieldsValidation: LoginFieldsValidation
|
||||
@Inject lateinit var loginErrorParser: LoginErrorParser
|
||||
@Inject lateinit var vectorFeatures: VectorFeatures
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedLoginBinding {
|
||||
return FragmentFtueCombinedLoginBinding.inflate(inflater, container, false)
|
||||
@ -70,6 +74,26 @@ class FtueAuthCombinedLoginFragment :
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content()))
|
||||
}
|
||||
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
|
||||
|
||||
viewModel.onEach(OnboardingViewState::canLoginWithQrCode) {
|
||||
configureQrCodeLoginButtonVisibility(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureQrCodeLoginButtonVisibility(canLoginWithQrCode: Boolean) {
|
||||
views.loginWithQrCode.isVisible = canLoginWithQrCode
|
||||
if (canLoginWithQrCode) {
|
||||
views.loginWithQrCode.debouncedClicks {
|
||||
navigator
|
||||
.openLoginWithQrCode(
|
||||
requireActivity(),
|
||||
QrCodeLoginArgs(
|
||||
loginType = QrCodeLoginType.LOGIN,
|
||||
showQrCodeImmediately = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
|
@ -24,11 +24,9 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorDummyViewState
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class QrCodeScannerViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VectorDummyViewState,
|
||||
val session: Session
|
||||
) : VectorViewModel<VectorDummyViewState, QrCodeScannerAction, QrCodeScannerEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -35,8 +35,11 @@ import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.login.qr.QrCodeLoginType
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
|
||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||
@ -63,6 +66,8 @@ class VectorSettingsDevicesFragment :
|
||||
|
||||
@Inject lateinit var colorProvider: ColorProvider
|
||||
|
||||
@Inject lateinit var vectorFeatures: VectorFeatures
|
||||
|
||||
@Inject lateinit var stringProvider: StringProvider
|
||||
|
||||
private val viewModel: DevicesViewModel by fragmentViewModel()
|
||||
@ -88,6 +93,7 @@ class VectorSettingsDevicesFragment :
|
||||
initWaitingView()
|
||||
initOtherSessionsView()
|
||||
initSecurityRecommendationsView()
|
||||
initQrLoginView()
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
@ -152,6 +158,38 @@ class VectorSettingsDevicesFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun initQrLoginView() {
|
||||
if (!vectorFeatures.isReciprocateQrCodeLogin()) {
|
||||
views.deviceListHeaderSignInWithQrCode.isVisible = false
|
||||
views.deviceListHeaderScanQrCodeButton.isVisible = false
|
||||
views.deviceListHeaderShowQrCodeButton.isVisible = false
|
||||
return
|
||||
}
|
||||
|
||||
views.deviceListHeaderSignInWithQrCode.isVisible = true
|
||||
views.deviceListHeaderScanQrCodeButton.isVisible = true
|
||||
views.deviceListHeaderShowQrCodeButton.isVisible = true
|
||||
|
||||
views.deviceListHeaderScanQrCodeButton.debouncedClicks {
|
||||
navigateToQrCodeScreen(showQrCodeImmediately = false)
|
||||
}
|
||||
|
||||
views.deviceListHeaderShowQrCodeButton.debouncedClicks {
|
||||
navigateToQrCodeScreen(showQrCodeImmediately = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToQrCodeScreen(showQrCodeImmediately: Boolean) {
|
||||
navigator
|
||||
.openLoginWithQrCode(
|
||||
requireActivity(),
|
||||
QrCodeLoginArgs(
|
||||
loginType = QrCodeLoginType.LINK_A_DEVICE,
|
||||
showQrCodeImmediately = showQrCodeImmediately,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
cleanUpLearnMoreButtonsListeners()
|
||||
super.onDestroyView()
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="ring"
|
||||
android:innerRadius="0dp"
|
||||
android:thicknessRatio="2"
|
||||
android:useLevel="false">
|
||||
|
||||
<solid android:color="?android:colorBackground" />
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?colorPrimary" />
|
||||
|
||||
</shape>
|
9
vector/src/main/res/drawable/ic_qr_code.xml
Normal file
9
vector/src/main/res/drawable/ic_qr_code.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="13dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="13"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M7.167,12V10.667H8.5V12H7.167ZM5.833,10.667V7.333H7.167V10.667H5.833ZM11.167,8.667V6H12.5V8.667H11.167ZM9.833,6V4.667H11.167V6H9.833ZM1.833,7.333V6H3.167V7.333H1.833ZM0.5,6V4.667H1.833V6H0.5ZM6.5,1.333V0H7.833V1.333H6.5ZM1.333,3.167H3.667V0.833H1.333V3.167ZM1,4C0.856,4 0.736,3.953 0.642,3.858C0.547,3.764 0.5,3.644 0.5,3.5V0.5C0.5,0.356 0.547,0.236 0.642,0.142C0.736,0.047 0.856,0 1,0H4C4.144,0 4.264,0.047 4.358,0.142C4.453,0.236 4.5,0.356 4.5,0.5V3.5C4.5,3.644 4.453,3.764 4.358,3.858C4.264,3.953 4.144,4 4,4H1ZM1.333,11.167H3.667V8.833H1.333V11.167ZM1,12C0.856,12 0.736,11.953 0.642,11.858C0.547,11.764 0.5,11.644 0.5,11.5V8.5C0.5,8.356 0.547,8.236 0.642,8.142C0.736,8.047 0.856,8 1,8H4C4.144,8 4.264,8.047 4.358,8.142C4.453,8.236 4.5,8.356 4.5,8.5V11.5C4.5,11.644 4.453,11.764 4.358,11.858C4.264,11.953 4.144,12 4,12H1ZM9.333,3.167H11.667V0.833H9.333V3.167ZM9,4C8.856,4 8.736,3.953 8.642,3.858C8.547,3.764 8.5,3.644 8.5,3.5V0.5C8.5,0.356 8.547,0.236 8.642,0.142C8.736,0.047 8.856,0 9,0H12C12.144,0 12.264,0.047 12.358,0.142C12.453,0.236 12.5,0.356 12.5,0.5V3.5C12.5,3.644 12.453,3.764 12.358,3.858C12.264,3.953 12.144,4 12,4H9ZM9.833,12V10H8.5V8.667H11.167V10.667H12.5V12H9.833ZM7.167,7.333V6H9.833V7.333H7.167ZM4.5,7.333V6H3.167V4.667H7.167V6H5.833V7.333H4.5ZM5.167,4V1.333H6.5V2.667H7.833V4H5.167ZM2,2.5V1.5H3V2.5H2ZM2,10.5V9.5H3V10.5H2ZM10,2.5V1.5H11V2.5H10Z"
|
||||
android:fillColor="#0DBD8B"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="54"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M53.667,19C53.667,17.533 52.467,16.333 51,16.333H45.48C44.173,7.293 36.413,0.333 27,0.333C17.587,0.333 9.827,7.293 8.52,16.333H3C1.533,16.333 0.333,17.533 0.333,19C0.333,20.467 1.533,21.667 3,21.667H8.52C9.827,30.707 17.587,37.667 27,37.667C36.413,37.667 44.173,30.707 45.48,21.667H51C52.467,21.667 53.667,20.467 53.667,19ZM35,25.667C35,27.133 33.8,28.333 32.333,28.333H21.667C20.2,28.333 19,27.133 19,25.667V17.667C19,16.2 20.2,15 21.667,15V12.333C21.667,9.107 24.547,6.52 27.907,7.08C30.52,7.507 32.333,9.96 32.333,12.627V15C33.8,15 35,16.2 35,17.667V25.667ZM29,21.667C29,22.76 28.093,23.667 27,23.667C25.907,23.667 25,22.76 25,21.667C25,20.573 25.907,19.667 27,19.667C28.093,19.667 29,20.573 29,21.667ZM29.667,12.333V15H24.333V12.333C24.333,10.867 25.533,9.667 27,9.667C28.467,9.667 29.667,10.867 29.667,12.333Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
10
vector/src/main/res/drawable/ic_qr_code_login_failed.xml
Normal file
10
vector/src/main/res/drawable/ic_qr_code_login_failed.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M20,40C31.046,40 40,31.046 40,20C40,8.954 31.046,0 20,0C8.954,0 0,8.954 0,20C0,31.046 8.954,40 20,40ZM18.084,14.688C18.007,13.809 18.655,13.037 19.535,12.976C20.399,12.914 21.17,13.562 21.263,14.441V14.688L20.769,20.86C20.723,21.431 20.244,21.863 19.674,21.863H19.581C19.041,21.816 18.624,21.4 18.578,20.86L18.084,14.688ZM21.015,24.887C21.015,25.637 20.407,26.244 19.657,26.244C18.907,26.244 18.3,25.637 18.3,24.887C18.3,24.137 18.907,23.529 19.657,23.529C20.407,23.529 21.015,24.137 21.015,24.887Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
@ -194,9 +194,9 @@
|
||||
android:text="@string/ftue_auth_forgot_password"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?colorSecondary"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintBottom_toTopOf="@id/actionSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginPasswordInput" />
|
||||
|
||||
@ -244,6 +244,20 @@
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSubmit" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/loginWithQrCode"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/login_scan_qr_code"
|
||||
android:visibility="gone"
|
||||
app:drawableLeftCompat="@drawable/ic_qr_code"
|
||||
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.app.features.login.SocialLoginButtonsView
|
||||
android:id="@+id/ssoButtons"
|
||||
android:layout_width="0dp"
|
||||
@ -252,7 +266,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginWithQrCode"
|
||||
tools:signMode="signup" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginInstructionsHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_scan_qr_code_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_camera"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_scan_qr_code_title" />
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginInstructionsView
|
||||
android:id="@+id/qrCodeLoginInstructionsView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginInstructionsHeaderView"
|
||||
app:qrCodeLoginInstruction1="@string/qr_code_login_new_device_instruction_1"
|
||||
app:qrCodeLoginInstruction2="@string/qr_code_login_new_device_instruction_2"
|
||||
app:qrCodeLoginInstruction3="@string/qr_code_login_new_device_instruction_3" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginInstructionsShowQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/qr_code_login_show_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qrCodeLoginInstructionsAlternativeLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginInstructionsShowQrCodeButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@drawable/divider_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginInstructionsAlternativeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:text="@string/qr_code_login_signing_in_a_mobile_device"
|
||||
app:drawableLeftCompat="@drawable/divider_horizontal"
|
||||
app:drawableTint="@color/alert_default_error_background" />
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginInstructionsScanQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/qr_code_login_scan_qr_code_button"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginInstructionsAlternativeLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginShowQrCodeHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_show_qr_code_link_a_device_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_camera"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_show_qr_code_title" />
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginInstructionsView
|
||||
android:id="@+id/qrCodeLoginShowQrCodeInstructionsView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginShowQrCodeHeaderView"
|
||||
app:qrCodeLoginInstruction1="@string/qr_code_login_new_device_instruction_1"
|
||||
app:qrCodeLoginInstruction2="@string/qr_code_login_new_device_instruction_2"
|
||||
app:qrCodeLoginInstruction3="@string/qr_code_login_new_device_instruction_3" />
|
||||
|
||||
<im.vector.app.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/qrCodeLoginSHowQrCodeImageView"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginShowQrCodeInstructionsView"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginShowQrCodeCancelButton"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginShowQrCodeCancelButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/action_cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
155
vector/src/main/res/layout/fragment_qr_code_login_status.xml
Normal file
155
vector/src/main/res/layout/fragment_qr_code_login_status.xml
Normal file
@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qrCodeLoginStatusLoadingLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="100dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusLoadingTextView"
|
||||
style="@style/TextAppearance.Vector.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/qr_code_login_connecting_to_device" />
|
||||
|
||||
<include layout="@layout/item_loading" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginStatusHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_connected_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_qr_code_login_connected"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_connected_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusSecurityCode"
|
||||
style="@style/TextAppearance.Vector.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="80dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginStatusHeaderView"
|
||||
tools:text="28E-1B9-D0F-896"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qrCodeLoginStatusNoMatchLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusCancelButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@drawable/divider_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusNoMatchTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:text="@string/qr_code_login_status_no_match"
|
||||
app:drawableLeftCompat="@drawable/divider_horizontal"
|
||||
app:drawableTint="@color/alert_default_error_background" />
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginStatusTryAgainButton"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/qr_code_login_try_again"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusNoMatchLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusCancelButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeTextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/qr_code_login_confirm_security_code_description"
|
||||
app:layout_constraintStart_toEndOf="@id/qrCodeLoginConfirmSecurityCodeImageView"
|
||||
app:layout_constraintTop_toTopOf="@id/qrCodeLoginConfirmSecurityCodeImageView" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/qr_code_login_confirm_security_code"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginConfirmSecurityCodeTextView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginStatusCancelButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/action_cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -108,6 +109,47 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
|
||||
|
||||
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||
android:id="@+id/deviceListHeaderSignInWithQrCode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListOtherSessions"
|
||||
app:sessionsListHeaderHasLearnMoreLink="false"
|
||||
app:sessionsListHeaderDescription="@string/device_manager_sessions_sign_in_with_qr_code_description"
|
||||
app:sessionsListHeaderTitle="@string/device_manager_sessions_sign_in_with_qr_code_title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/deviceListHeaderScanQrCodeButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/qr_code_login_scan_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSignInWithQrCode"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/deviceListHeaderShowQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/qr_code_login_show_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderScanQrCodeButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<include
|
||||
android:id="@+id/waiting_view"
|
||||
layout="@layout/merge_overlay_waiting_view"
|
||||
|
48
vector/src/main/res/layout/view_qr_code_login_header.xml
Normal file
48
vector/src/main/res/layout/view_qr_code_login_header.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCodeLoginHeaderImageView"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:background="@drawable/circle"
|
||||
android:contentDescription="@string/qr_code_login_header_scan_qr_code_title"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?vctr_system"
|
||||
tools:backgroundTint="?colorPrimary"
|
||||
tools:src="@drawable/ic_camera" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginHeaderTitleTextView"
|
||||
style="@style/TextAppearance.Vector.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginHeaderImageView"
|
||||
tools:text="@string/qr_code_login_header_scan_qr_code_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginHeaderDescriptionTextView"
|
||||
style="@style/TextAppearance.Vector.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginHeaderTitleTextView"
|
||||
tools:text="@string/qr_code_login_header_scan_qr_code_description" />
|
||||
|
||||
</merge>
|
101
vector/src/main/res/layout/view_qr_code_login_instructions.xml
Normal file
101
vector/src/main/res/layout/view_qr_code_login_instructions.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions1Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/one"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction1TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions2Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instructions1Layout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/two"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction2TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_2" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions3Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instructions2Layout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/three"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction3TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_3" />
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
Loading…
Reference in New Issue
Block a user