diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index a13db6a6db..1c3daf9c3d 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3385,7 +3385,7 @@ The request was denied on the other device. Open ${app_name} on your other device Go to Settings -> Security & Privacy -> Show All Sessions - Select \'Show QR code\' + Select \'Show QR code in this device\' Start at the sign in screen Select \'Sign in with QR code\' Start at the sign in screen diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index d65e629b71..2c3cb440b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -20,7 +20,6 @@ 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 @@ -144,8 +143,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk() homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ - getVersionResult.doesServerSupportThreads().orFalse() - homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin().orFalse() + getVersionResult.doesServerSupportThreads() + homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt index d946f744cf..8854d0720f 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt @@ -20,6 +20,6 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class QrCodeLoginAction : VectorViewModelAction { data class OnQrCodeScanned(val qrCode: String) : QrCodeLoginAction() - object QrCodeViewStarted : QrCodeLoginAction() + object GenerateQrCode : QrCodeLoginAction() object ShowQrCode : QrCodeLoginAction() } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt index 29190a70cb..f5fd17c0c8 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt @@ -26,7 +26,6 @@ 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 org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber @AndroidEntryPoint @@ -45,7 +44,7 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { showInstructionsFragment(qrCodeLoginArgs) } QrCodeLoginType.LINK_A_DEVICE -> { - if (qrCodeLoginArgs.showQrCodeByDefault.orFalse()) { + if (qrCodeLoginArgs.showQrCodeImmediately) { handleNavigateToShowQrCodeScreen() } else { showInstructionsFragment(qrCodeLoginArgs) @@ -54,6 +53,7 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { null -> { Timber.i("QrCodeLoginArgs is null. This is not expected.") finish() + return } } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt index c7b681c488..6c23d07c0f 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt @@ -22,5 +22,5 @@ import kotlinx.parcelize.Parcelize @Parcelize data class QrCodeLoginArgs( val loginType: QrCodeLoginType, - val showQrCodeByDefault: Boolean, + val showQrCodeImmediately: Boolean, ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt index f14d711d3c..03478d2f50 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt @@ -24,6 +24,7 @@ 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( @@ -52,27 +53,26 @@ class QrCodeLoginHeaderView @JvmOverloads constructor( private fun setTitle(typedArray: TypedArray) { val title = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderTitle) - binding.qrCodeLoginHeaderTitleTextView.text = title + setTitle(title) } private fun setDescription(typedArray: TypedArray) { val description = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderDescription) - binding.qrCodeLoginHeaderDescriptionTextView.text = description + 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) - binding.qrCodeLoginHeaderImageView.setImageResource(imageResource) - binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTint) + setImage(imageResource, backgroundTint) } - fun setTitle(title: String) { - binding.qrCodeLoginHeaderTitleTextView.text = title + fun setTitle(title: String?) { + binding.qrCodeLoginHeaderTitleTextView.setTextOrHide(title) } - fun setDescription(description: String) { - binding.qrCodeLoginHeaderDescriptionTextView.text = description + fun setDescription(description: String?) { + binding.qrCodeLoginHeaderDescriptionTextView.setTextOrHide(description) } fun setImage(imageResource: Int, backgroundTintColor: Int) { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt index ae3ba9574b..efd23f2530 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt @@ -22,6 +22,7 @@ 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 @@ -41,32 +42,10 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment + 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), + ) + ) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt index 7ef1706b37..ed5c4de175 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt @@ -54,18 +54,19 @@ class QrCodeLoginInstructionsView @JvmOverloads constructor( val instruction1 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction1) val instruction2 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction2) val instruction3 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction3) - binding.instructions1Layout.isVisible = instruction1 != null - binding.instructions2Layout.isVisible = instruction2 != null - binding.instructions3Layout.isVisible = instruction3 != null - binding.instruction1TextView.text = instruction1 - binding.instruction2TextView.text = instruction2 - binding.instruction3TextView.text = instruction3 + setInstructions( + listOf( + instruction1, + instruction2, + instruction3, + ) + ) } - fun setInstructions(instructions: List) { - setInstruction(binding.instructions1Layout, binding.instruction1TextView, instructions.getOrNull(0)) - setInstruction(binding.instructions2Layout, binding.instruction2TextView, instructions.getOrNull(1)) - setInstruction(binding.instructions3Layout, binding.instruction3TextView, instructions.getOrNull(2)) + fun setInstructions(instructions: List?) { + 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?) { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt index efc2c9dd08..d31f531a49 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt @@ -21,6 +21,7 @@ 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 @@ -38,8 +39,7 @@ class QrCodeLoginShowQrCodeFragment : VectorBaseFragment - showQrCode(qrCodeData) - } - } - } - 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)) @@ -81,4 +72,11 @@ class QrCodeLoginShowQrCodeFragment : VectorBaseFragment + state.generatedQrCodeData?.let { qrCodeData -> + showQrCode(qrCodeData) + } + setInstructions(state.loginType) + } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 405e37af15..a9c589e469 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -22,6 +22,7 @@ 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 @@ -40,7 +41,6 @@ class QrCodeLoginStatusFragment : VectorBaseFragment handleConnectionEstablished(it.connectionStatus, it.loginType) - QrCodeLoginConnectionStatus.ConnectingToDevice -> handleConnectingToDevice() - QrCodeLoginConnectionStatus.SigningIn -> handleSigningIn() - is QrCodeLoginConnectionStatus.Failed -> handleFailed(it.connectionStatus) - null -> { /* NOOP */ } - } - } - } - private fun handleFailed(connectionStatus: QrCodeLoginConnectionStatus.Failed) { views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false views.qrCodeLoginStatusLoadingLayout.isVisible = false @@ -128,4 +116,14 @@ class QrCodeLoginStatusFragment : VectorBaseFragment + 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 */ } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index da3348653c..e979ffa63c 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -23,8 +23,7 @@ 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 kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, @@ -40,7 +39,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( override fun handle(action: QrCodeLoginAction) { when (action) { is QrCodeLoginAction.OnQrCodeScanned -> handleOnQrCodeScanned(action) - QrCodeLoginAction.QrCodeViewStarted -> handleQrCodeViewStarted() + QrCodeLoginAction.GenerateQrCode -> handleQrCodeViewStarted() QrCodeLoginAction.ShowQrCode -> handleShowQrCode() } } @@ -67,18 +66,6 @@ class QrCodeLoginViewModel @AssistedInject constructor( } _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) } - - // TODO. UI test purpose. Fixme remove! - viewModelScope.launch { - delay(3000) - onFailed(QrCodeLoginErrorType.TIMEOUT, true) - delay(3000) - onConnectionEstablished("1234-ABCD-5678-EFGH") - delay(3000) - onSigningIn() - delay(3000) - onFailed(QrCodeLoginErrorType.DEVICE_IS_NOT_SUPPORTED, false) - } } private fun onFailed(errorType: QrCodeLoginErrorType, canTryAgain: Boolean) { @@ -106,17 +93,14 @@ class QrCodeLoginViewModel @AssistedInject constructor( } } - /** - * TODO. UI test purpose. Fixme accordingly. - */ + // TODO. Implement in the logic related PR. private fun isValidQrCode(qrCode: String): Boolean { - return qrCode.startsWith("http") + Timber.d("isValidQrCode: $qrCode") + return false } - /** - * TODO. UI test purpose. Fixme accordingly. - */ + // TODO. Implement in the logic related PR. private fun generateQrCodeData(): String { - return "https://element.io" + return "TODO" } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index fb8c29f39f..46b14c6d5f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -118,7 +118,7 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun observeQrCodeLoginCapability(homeServerUrl: String) = viewModelScope.launch { + private fun checkQrCodeLoginCapability(homeServerUrl: String) { if (!vectorFeatures.isQrCodeLoginEnabled()) { setState { copy( @@ -133,13 +133,15 @@ class OnboardingViewModel @AssistedInject constructor( ) } } else { - // check if selected server supports MSC3882 first - homeServerConnectionConfigFactory.create(homeServerUrl)?.let { - val canLoginWithQrCode = authenticationService.isQrLoginSupported(it) - setState { - copy( - canLoginWithQrCode = canLoginWithQrCode - ) + viewModelScope.launch { + // check if selected server supports MSC3882 first + homeServerConnectionConfigFactory.create(homeServerUrl)?.let { + val canLoginWithQrCode = authenticationService.isQrLoginSupported(it) + setState { + copy( + canLoginWithQrCode = canLoginWithQrCode + ) + } } } } @@ -707,7 +709,7 @@ class OnboardingViewModel @AssistedInject constructor( _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction) - observeQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString()) + checkQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString()) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index 4d32964b7d..aad54877c9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -81,20 +81,18 @@ class FtueAuthCombinedLoginFragment : } private fun configureQrCodeLoginButtonVisibility(canLoginWithQrCode: Boolean) { + views.loginWithQrCode.isVisible = canLoginWithQrCode if (canLoginWithQrCode) { - views.loginWithQrCode.isVisible = true views.loginWithQrCode.debouncedClicks { navigator .openLoginWithQrCode( requireActivity(), QrCodeLoginArgs( loginType = QrCodeLoginType.LOGIN, - showQrCodeByDefault = false, + showQrCodeImmediately = false, ) ) } - } else { - views.loginWithQrCode.isVisible = false } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index d4ee1d92eb..080405c2ce 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -170,28 +170,25 @@ class VectorSettingsDevicesFragment : views.deviceListHeaderShowQrCodeButton.isVisible = true views.deviceListHeaderScanQrCodeButton.debouncedClicks { - navigator - .openLoginWithQrCode( - requireActivity(), - QrCodeLoginArgs( - loginType = QrCodeLoginType.LINK_A_DEVICE, - showQrCodeByDefault = false, - ) - ) + navigateToQrCodeScreen(showQrCodeImmediately = false) } views.deviceListHeaderShowQrCodeButton.debouncedClicks { - navigator - .openLoginWithQrCode( - requireActivity(), - QrCodeLoginArgs( - loginType = QrCodeLoginType.LINK_A_DEVICE, - showQrCodeByDefault = true, - ) - ) + 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() diff --git a/vector/src/main/res/layout/fragment_ftue_combined_login.xml b/vector/src/main/res/layout/fragment_ftue_combined_login.xml index 17fe895cf2..7eff92f4f9 100644 --- a/vector/src/main/res/layout/fragment_ftue_combined_login.xml +++ b/vector/src/main/res/layout/fragment_ftue_combined_login.xml @@ -251,10 +251,12 @@ 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" /> + app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader" + tools:visibility="visible" /> - - - - -