Simulate qr login states.

This commit is contained in:
Onuray Sahin 2022-10-06 18:17:02 +03:00
parent a66b183bf7
commit a00afa7a30
9 changed files with 212 additions and 7 deletions

View File

@ -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.live.map.LiveLocationMapViewModel
import im.vector.app.features.location.preview.LocationPreviewViewModel import im.vector.app.features.location.preview.LocationPreviewViewModel
import im.vector.app.features.login.LoginViewModel 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.matrixto.MatrixToBottomSheetViewModel
import im.vector.app.features.media.VectorAttachmentViewerViewModel import im.vector.app.features.media.VectorAttachmentViewerViewModel
import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewModel
@ -659,4 +660,9 @@ interface MavericksViewModelModule {
@IntoMap @IntoMap
@MavericksViewModelKey(RenameSessionViewModel::class) @MavericksViewModelKey(RenameSessionViewModel::class)
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *> fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(QrCodeLoginViewModel::class)
fun qrCodeLoginViewModelFactory(factory: QrCodeLoginViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
} }

View File

@ -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()
}

View File

@ -20,6 +20,8 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.SimpleFragmentActivity
@ -27,11 +29,13 @@ import im.vector.app.core.platform.SimpleFragmentActivity
@AndroidEntryPoint @AndroidEntryPoint
class QrCodeLoginActivity : SimpleFragmentActivity() { class QrCodeLoginActivity : SimpleFragmentActivity() {
private val viewModel: QrCodeLoginViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
views.toolbar.visibility = View.GONE 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 (isFirstCreation()) {
if (qrCodeLoginArgs?.loginType == QrCodeLoginType.LOGIN) { if (qrCodeLoginArgs?.loginType == QrCodeLoginType.LOGIN) {
addFragment( 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 { companion object {
private const val EXTRA_QR_CODE_LOGIN_ARGS = "EXTRA_QR_CODE_LOGIN_ARGS"
fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent { fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent {
return Intent(context, QrCodeLoginActivity::class.java).apply { return Intent(context, QrCodeLoginActivity::class.java).apply {
putExtra(EXTRA_QR_CODE_LOGIN_ARGS, qrCodeLoginArgs) putExtra(Mavericks.KEY_ARG, qrCodeLoginArgs)
} }
} }
} }

View File

@ -66,4 +66,17 @@ class QrCodeLoginHeaderView @JvmOverloads constructor(
binding.qrCodeLoginHeaderImageView.setImageResource(imageResource) binding.qrCodeLoginHeaderImageView.setImageResource(imageResource)
binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTint) 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)
}
} }

View File

@ -21,6 +21,8 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding
@ -29,6 +31,8 @@ import timber.log.Timber
class QrCodeLoginInstructionsFragment : VectorBaseFragment<FragmentQrCodeLoginInstructionsBinding>() { class QrCodeLoginInstructionsFragment : VectorBaseFragment<FragmentQrCodeLoginInstructionsBinding>() {
private val viewModel: QrCodeLoginViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initScanQrCodeButton() initScanQrCodeButton()
@ -58,7 +62,7 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment<FragmentQrCodeLoginIn
} }
private fun onQrCodeScanned(scannedQrCode: String) { private fun onQrCodeScanned(scannedQrCode: String) {
Timber.d("QrCodeLoginInstructionsFragment.onQrCodeScanned $scannedQrCode") viewModel.handle(QrCodeLoginAction.OnQrCodeScanned(scannedQrCode))
} }
private fun onQrCodeScannerFailed() { private fun onQrCodeScannerFailed() {

View File

@ -20,17 +20,67 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding
import im.vector.app.features.themes.ThemeUtils
class QrCodeLoginStatusFragment : VectorBaseFragment<FragmentQrCodeLoginStatusBinding>() { class QrCodeLoginStatusFragment : VectorBaseFragment<FragmentQrCodeLoginStatusBinding>() {
private val viewModel: QrCodeLoginViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginStatusBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginStatusBinding {
return FragmentQrCodeLoginStatusBinding.inflate(inflater, container, false) return FragmentQrCodeLoginStatusBinding.inflate(inflater, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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)
)
} }
} }

View File

@ -19,5 +19,5 @@ package im.vector.app.features.login.qr
import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewEvents
sealed class QrCodeLoginViewEvents : VectorViewEvents { sealed class QrCodeLoginViewEvents : VectorViewEvents {
object NavigateToStatusScreen : QrCodeLoginViewEvents()
} }

View File

@ -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<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)
}
}
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")
}
}

View File

@ -3,7 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?android:colorBackground"
android:paddingHorizontal="16dp">
<LinearLayout <LinearLayout
android:id="@+id/qrCodeLoginStatusLoadingLayout" android:id="@+id/qrCodeLoginStatusLoadingLayout"
@ -55,7 +57,7 @@
tools:visibility="visible" /> tools:visibility="visible" />
<FrameLayout <FrameLayout
android:id="@+id/qrCodeLoginStatusAlternativeLayout" android:id="@+id/qrCodeLoginStatusNoMatchLayout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"