Skip to content

Commit

Permalink
Refactor: the main fragment/vm pair to support scanning using mlkit
Browse files Browse the repository at this point in the history
  • Loading branch information
zwarm committed Feb 1, 2024
1 parent 6f1fc10 commit cd99536
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker.Stat
import org.wordpress.android.ui.barcodescanner.BarcodeScanningFragment
import org.wordpress.android.ui.barcodescanner.BarcodeScanningFragment.Companion.KEY_BARCODE_SCANNING_REQUEST
import org.wordpress.android.ui.barcodescanner.BarcodeScanningFragment.Companion.KEY_BARCODE_SCANNING_SCAN_STATUS
import org.wordpress.android.ui.barcodescanner.CodeScannerStatus
import org.wordpress.android.ui.compose.components.VerticalScrollBox
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.ui.posts.BasicDialogViewModel
Expand Down Expand Up @@ -49,6 +55,14 @@ class QRCodeAuthFragment : Fragment() {
private val qrCodeAuthViewModel: QRCodeAuthViewModel by viewModels()
private val dialogViewModel: BasicDialogViewModel by activityViewModels()

@Suppress("DEPRECATION")
private val resultListener = FragmentResultListener { requestKey, result ->
if (requestKey == KEY_BARCODE_SCANNING_REQUEST) {
val resultValue = result.getParcelable<CodeScannerStatus?>(KEY_BARCODE_SCANNING_SCAN_STATUS)
resultValue?.let { qrCodeAuthViewModel.handleScanningResult(it) }
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -64,6 +78,7 @@ class QRCodeAuthFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
initBackPressHandler()
initViewModel(savedInstanceState)
initScannerResultListener()
observeViewModel()
}

Expand All @@ -80,13 +95,22 @@ class QRCodeAuthFragment : Fragment() {
qrCodeAuthViewModel.start(uri, isDeepLink, savedInstanceState)
}

private fun initScannerResultListener() {
requireActivity().supportFragmentManager.setFragmentResultListener(
KEY_BARCODE_SCANNING_REQUEST,
viewLifecycleOwner,
resultListener
)
}

private fun handleActionEvents(actionEvent: QRCodeAuthActionEvent) {
when (actionEvent) {
is LaunchDismissDialog -> launchDismissDialog(actionEvent.dialogModel)
is LaunchScanner -> launchScanner()
is FinishActivity -> requireActivity().finish()
}
}

private fun launchDismissDialog(model: QRCodeAuthDialogModel) {
dialogViewModel.showDialog(
requireActivity().supportFragmentManager,
Expand All @@ -96,24 +120,30 @@ class QRCodeAuthFragment : Fragment() {
getString(model.message),
getString(model.positiveButtonLabel),
model.negativeButtonLabel?.let { label -> getString(label) },
model.cancelButtonLabel?.let { label -> getString(label) }
model.cancelButtonLabel?.let { label -> getString(label) },
false
)
)
}

private fun launchScanner() {
qrCodeAuthViewModel.track(Stat.QRLOGIN_SCANNER_DISPLAYED)
val scanner = GmsBarcodeScanning.getClient(requireContext())
scanner.startScan()
.addOnSuccessListener { barcode -> qrCodeAuthViewModel.onScanSuccess(barcode.rawValue) }
.addOnFailureListener { qrCodeAuthViewModel.onScanFailure() }
replaceFragment(BarcodeScanningFragment())
}

private fun replaceFragment(fragment: Fragment) {
val transaction: FragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.addToBackStack(null)
transaction.commit()
}

private fun initBackPressHandler() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
qrCodeAuthViewModel.onBackPressed()
}
}

override fun onSaveInstanceState(outState: Bundle) {
qrCodeAuthViewModel.writeToBundle(outState)
super.onSaveInstanceState(outState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import org.wordpress.android.fluxc.network.rest.wpcom.qrcodeauth.QRCodeAuthError
import org.wordpress.android.fluxc.store.qrcodeauth.QRCodeAuthStore
import org.wordpress.android.fluxc.store.qrcodeauth.QRCodeAuthStore.QRCodeAuthResult
import org.wordpress.android.fluxc.store.qrcodeauth.QRCodeAuthStore.QRCodeAuthValidateResult
import org.wordpress.android.ui.barcodescanner.BarcodeScanningTracker
import org.wordpress.android.ui.barcodescanner.CodeScannerStatus
import org.wordpress.android.ui.barcodescanner.ScanningSource
import org.wordpress.android.ui.posts.BasicDialogViewModel.DialogInteraction
import org.wordpress.android.ui.posts.BasicDialogViewModel.DialogInteraction.Dismissed
import org.wordpress.android.ui.posts.BasicDialogViewModel.DialogInteraction.Negative
Expand Down Expand Up @@ -52,19 +55,23 @@ class QRCodeAuthViewModel @Inject constructor(
private val uiStateMapper: QRCodeAuthUiStateMapper,
private val networkUtilsWrapper: NetworkUtilsWrapper,
private val validator: QRCodeAuthValidator,
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper,
private val barcodeScanningTracker: BarcodeScanningTracker
) : ViewModel() {
private val _actionEvents = Channel<QRCodeAuthActionEvent>(Channel.BUFFERED)
val actionEvents = _actionEvents.receiveAsFlow()

private val _uiState = MutableStateFlow<QRCodeAuthUiState>(Loading)
val uiState: StateFlow<QRCodeAuthUiState> = _uiState

private var trackingOrigin: String? = null
private var data: String? = null
private var token: String? = null
private var location: String? = null
private var browser: String? = null
private var lastState: QRCodeAuthUiStateType? = null
private var isStarted = false

fun start(uri: String? = null, isDeepLink: Boolean = false, savedInstanceState: Bundle? = null) {
if (isStarted) return
isStarted = true
Expand Down Expand Up @@ -100,7 +107,6 @@ class QRCodeAuthViewModel @Inject constructor(
this::onAuthenticateCancelClicked
)
)

AUTHENTICATING -> postUiState(uiStateMapper.mapToAuthenticating(location = location, browser = browser))
DONE -> postUiState(uiStateMapper.mapToDone(this::onDismissClicked))
// errors
Expand All @@ -111,25 +117,36 @@ class QRCodeAuthViewModel @Inject constructor(
this::onCancelClicked
)
)

EXPIRED_TOKEN -> postUiState(uiStateMapper.mapToExpired(this::onScanAgainClicked, this::onCancelClicked))
NO_INTERNET -> {
postUiState(uiStateMapper.mapToNoInternet(this::onScanAgainClicked, this::onCancelClicked))
}

else -> updateUiStateAndLaunchScanner()
}
}
fun handleScanningResult(status: CodeScannerStatus) {
when (status) {
is CodeScannerStatus.Success -> onScanSuccess(status.code)
is CodeScannerStatus.Failure -> onScanFailure(status)
is CodeScannerStatus.Exit -> onExit()
is CodeScannerStatus.NavigateUp -> onBackPressed()
}
}

// https://apps.wordpress.com/get/?campaign=login-qr-code#qr-code-login?token=asdfadsfa&data=asdfasdf
fun onScanSuccess(scannedValue: String?) {
barcodeScanningTracker.trackSuccess(ScanningSource.QRCODE_LOGIN)
track(Stat.QRLOGIN_SCANNER_SCANNED_CODE)
process(scannedValue)
}

fun onScanFailure() {
// Note: This is a result of the tap on "X" within the scanner view
track(Stat.QRLOGIN_SCANNER_DISMISSED)
fun onScanFailure(status: CodeScannerStatus.Failure) {
barcodeScanningTracker.trackScanFailure(ScanningSource.QRCODE_LOGIN, status.type)
postActionEvent(FinishActivity)
}

private fun onExit() {
track(Stat.QRLOGIN_SCANNER_DISMISSED_CAMERA_PERMISSION_DENIED)
postActionEvent(FinishActivity)
}

Expand Down Expand Up @@ -282,8 +299,11 @@ class QRCodeAuthViewModel @Inject constructor(

fun onDialogInteraction(interaction: DialogInteraction) {
when (interaction) {
is Positive -> postActionEvent(FinishActivity)
is Negative -> Unit
is Positive -> {
track(Stat.QRLOGIN_SCANNER_DISMISSED)
postActionEvent(FinishActivity)
}
is Negative -> onScanAgainClicked()
is Dismissed -> Unit
}
}
Expand Down

0 comments on commit cd99536

Please sign in to comment.