From a00afa7a3080f3ec71c82330d4c5fb6cb5e6a83d Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 6 Oct 2022 18:17:02 +0300 Subject: [PATCH] Simulate qr login states. --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../features/login/qr/QrCodeLoginAction.kt | 23 +++++ .../features/login/qr/QrCodeLoginActivity.kt | 26 +++++- .../login/qr/QrCodeLoginHeaderView.kt | 13 +++ .../qr/QrCodeLoginInstructionsFragment.kt | 6 +- .../login/qr/QrCodeLoginStatusFragment.kt | 50 +++++++++++ .../login/qr/QrCodeLoginViewEvents.kt | 2 +- .../features/login/qr/QrCodeLoginViewModel.kt | 87 +++++++++++++++++++ .../layout/fragment_qr_code_login_status.xml | 6 +- 9 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 62e7140742..76e15cd5cf 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -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 @@ -659,4 +660,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(RenameSessionViewModel::class) fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(QrCodeLoginViewModel::class) + fun qrCodeLoginViewModelFactory(factory: QrCodeLoginViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } 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 new file mode 100644 index 0000000000..948a771118 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt @@ -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 + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class QrCodeLoginAction : VectorViewModelAction { + data class OnQrCodeScanned(val qrCode: String) : 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 bef9d5a040..5e359bfb26 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 @@ -20,6 +20,8 @@ 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 @@ -27,11 +29,13 @@ import im.vector.app.core.platform.SimpleFragmentActivity @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?.getParcelable(EXTRA_QR_CODE_LOGIN_ARGS) + val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelable(Mavericks.KEY_ARG) if (isFirstCreation()) { if (qrCodeLoginArgs?.loginType == QrCodeLoginType.LOGIN) { addFragment( @@ -41,14 +45,30 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { ) } } + + observeViewEvents() + } + + private fun observeViewEvents() { + viewModel.observeViewEvents { + when (it) { + QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen() + } + } + } + + private fun handleNavigateToStatusScreen() { + addFragment( + views.container, + QrCodeLoginStatusFragment::class.java + ) } companion object { - private const val EXTRA_QR_CODE_LOGIN_ARGS = "EXTRA_QR_CODE_LOGIN_ARGS" fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent { return Intent(context, QrCodeLoginActivity::class.java).apply { - putExtra(EXTRA_QR_CODE_LOGIN_ARGS, qrCodeLoginArgs) + putExtra(Mavericks.KEY_ARG, qrCodeLoginArgs) } } } 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 315369c462..f14d711d3c 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 @@ -66,4 +66,17 @@ class QrCodeLoginHeaderView @JvmOverloads constructor( binding.qrCodeLoginHeaderImageView.setImageResource(imageResource) binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTint) } + + fun setTitle(title: String) { + binding.qrCodeLoginHeaderTitleTextView.text = title + } + + fun setDescription(description: String) { + binding.qrCodeLoginHeaderDescriptionTextView.text = description + } + + fun setImage(imageResource: Int, backgroundTintColor: Int) { + binding.qrCodeLoginHeaderImageView.setImageResource(imageResource) + binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor) + } } 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 d15d01ec12..99dea345eb 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 @@ -21,6 +21,8 @@ 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.fragmentViewModel import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding @@ -29,6 +31,8 @@ import timber.log.Timber class QrCodeLoginInstructionsFragment : VectorBaseFragment() { + private val viewModel: QrCodeLoginViewModel by activityViewModel() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initScanQrCodeButton() @@ -58,7 +62,7 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment() { + 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) + observeViewState() + } + private fun observeViewState() { + viewModel.onEach { + when (it.connectionStatus) { + is QrCodeLoginConnectionStatus.Connected -> handleConnectionEstablished(it.connectionStatus) + QrCodeLoginConnectionStatus.ConnectingToDevice -> handleConnectingToDevice() + QrCodeLoginConnectionStatus.SigningIn -> handleSigningIn() + null -> TODO() + } + } + } + + private fun handleConnectingToDevice() { + views.qrCodeLoginStatusLoadingLayout.isVisible = true + views.qrCodeLoginStatusHeaderView.isVisible = false + views.qrCodeLoginStatusSecurityCode.isVisible = false + views.qrCodeLoginStatusNoMatchLayout.isVisible = false + views.qrCodeLoginStatusCancelButton.isVisible = true + views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_connecting_to_device) + } + + private fun handleSigningIn() { + views.qrCodeLoginStatusLoadingLayout.isVisible = true + views.qrCodeLoginStatusHeaderView.isVisible = false + views.qrCodeLoginStatusSecurityCode.isVisible = false + views.qrCodeLoginStatusNoMatchLayout.isVisible = false + views.qrCodeLoginStatusCancelButton.isVisible = false + views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_signing_in) + } + + private fun handleConnectionEstablished(connectionStatus: QrCodeLoginConnectionStatus.Connected) { + views.qrCodeLoginStatusLoadingLayout.isVisible = false + views.qrCodeLoginStatusHeaderView.isVisible = true + views.qrCodeLoginStatusSecurityCode.isVisible = true + views.qrCodeLoginStatusNoMatchLayout.isVisible = true + views.qrCodeLoginStatusCancelButton.isVisible = true + 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) + ) } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt index f5228f1d41..b8ebb65308 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt @@ -19,5 +19,5 @@ package im.vector.app.features.login.qr import im.vector.app.core.platform.VectorViewEvents sealed class QrCodeLoginViewEvents : VectorViewEvents { - + object NavigateToStatusScreen : QrCodeLoginViewEvents() } 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 new file mode 100644 index 0000000000..ac3b68da15 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -0,0 +1,87 @@ +/* + * 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 kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class QrCodeLoginViewModel @AssistedInject constructor( + @Assisted private val initialState: QrCodeLoginViewState, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: QrCodeLoginAction) { + when (action) { + is QrCodeLoginAction.OnQrCodeScanned -> handleOnQrCodeScanned(action) + } + } + + private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) { + if (isValidQrCode(action.qrCode)) { + setState { + copy( + connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice + ) + } + _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) + } + + // TODO. UI test purpose. Fixme remove! + viewModelScope.launch { + delay(3000) + onConnectionEstablished("1234-ABCD-5678-EFGH") + delay(3000) + onSigningIn() + } + } + + private fun onConnectionEstablished(securityCode: String) { + setState { + copy( + connectionStatus = QrCodeLoginConnectionStatus.Connected(securityCode) + ) + } + } + + private fun onSigningIn() { + setState { + copy( + connectionStatus = QrCodeLoginConnectionStatus.SigningIn + ) + } + } + + /** + * TODO. UI test purpose. Fixme accordingly. + */ + private fun isValidQrCode(qrCode: String): Boolean { + return qrCode.startsWith("http") + } +} diff --git a/vector/src/main/res/layout/fragment_qr_code_login_status.xml b/vector/src/main/res/layout/fragment_qr_code_login_status.xml index 7fe8ea6c02..0c200eeb83 100644 --- a/vector/src/main/res/layout/fragment_qr_code_login_status.xml +++ b/vector/src/main/res/layout/fragment_qr_code_login_status.xml @@ -3,7 +3,9 @@ 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:layout_height="match_parent" + android:background="?android:colorBackground" + android:paddingHorizontal="16dp">