Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
package com.ultron.ar.presentation.argameplay
import android.Manifest
import android.os.Bundle
import android.view.View
import android.widget.TextView
import com.domain.model.jobdetails.JobDetailsDomain
import com.domain.model.route.Type
import com.domain.usecase.games.SubmitAnswerUseCase
import com.google.ar.core.Config
import com.google.ar.core.TrackingState
import com.google.ar.sceneform.FrameTime
import com.google.ar.sceneform.Scene
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.ultron.ar.arlocation.extensions.extensions.ArSceneLifecycleUtil
import com.ultron.ar.arlocation.extensions.extensions.GlobalPosition
import com.ultron.ar.arlocation.extensions.extensions.LocationArScene
import com.ultron.ar.arlocation.extensions.model.NavigationMarker
import com.ultron.ar.presentation.BaseFragment
import com.ultron.ar.presentation.R
import com.ultron.ar.presentation.argameplay.viewmodels.ArViewModel
import com.ultron.ar.presentation.argameplay.viewmodels.ObjectRecognizeViewModel
import com.ultron.ar.presentation.argameplay.viewmodels.QrRecognizeViewModel
import com.ultron.ar.presentation.argameplay.viewmodels.TextRecognizerViewModel
import com.ultron.ar.presentation.gameplay.GamePlayUiState
import com.ultron.ar.presentation.gameplay.GamePlayViewModel
import com.ultron.ar.utils.extensions.toJson
import kotlinx.android.synthetic.main.fragment_ar_gameplay.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import utils.*
import java.util.*
const val JOB_DETAILS = "job_details"
open class ArGamePlayFragment : BaseFragment(), Scene.OnUpdateListener {
private lateinit var arLifeCycle: ArSceneLifecycleUtil
private lateinit var locationArScene: LocationArScene
private lateinit var jobDetailsDomain: JobDetailsDomain
private var currentTrackingState: TrackingState = TrackingState.STOPPED
private val gamePlayViewModel: GamePlayViewModel by lazy {
getViewModel(getNavigationActivity(), GamePlayViewModel::class.java)
}
private val arViewModel: ArViewModel by lazy {
getViewModel(getNavigationActivity(), ArViewModel::class.java)
}
private val objectRecognizeViewModel: ObjectRecognizeViewModel by lazy {
getViewModel(this, ObjectRecognizeViewModel::class.java)
}
private val qrRecognizeViewModel: QrRecognizeViewModel by lazy {
getViewModel(this, QrRecognizeViewModel::class.java)
}
private val textRecognizerViewModel: TextRecognizerViewModel by lazy {
getViewModel(this, TextRecognizerViewModel::class.java)
}
companion object {
fun getInstance(jobDetails: JobDetailsDomain) =
ArGamePlayFragment().apply {
arguments = Bundle().apply {
putParcelable(JOB_DETAILS, jobDetails)
}
}
}
override fun setLayout() = R.layout.fragment_ar_gameplay
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
jobDetailsDomain = arguments?.getParcelable<JobDetailsDomain>(JOB_DETAILS) as JobDetailsDomain
sceneView.session?.configure(sceneView.session?.config?.apply {
focusMode = Config.FocusMode.AUTO
})
gamePlayViewModel.getGamePlayUiState().listen(viewLifecycleOwner) { state ->
if (state is GamePlayUiState.Execution) {
listenToDetectorsAnswers(jobDetailsDomain.type)
scanView.show()
north.hide()
}
}
checkPermissions()
}
override fun onResume() {
super.onResume()
if (::arLifeCycle.isInitialized)
arLifeCycle.onActivityResume()
}
override fun onPause() {
super.onPause()
if (::arLifeCycle.isInitialized)
arLifeCycle.onActivityPause()
}
override fun onDestroy() {
super.onDestroy()
if (::arLifeCycle.isInitialized)
arLifeCycle.onActivityDestroy()
}
override fun onUpdate(frame: FrameTime?) {
checkTrackingState(trackingState = sceneView.arFrame?.camera?.trackingState
?: TrackingState.STOPPED)
updateArMarkersUi()
invalidateDetector()
}
private fun invalidateDetector() {
if (arViewModel.shouldProcessFrame()) {
GlobalScope.launch(Dispatchers.IO) {
when (jobDetailsDomain.type) {
JobDetailsDomain.FIND_OBJECT_JOB -> objectRecognizeViewModel.recognizeObject(sceneView.arFrame)
JobDetailsDomain.TEXT_ANSWER_JOB -> textRecognizerViewModel.recognizeText(sceneView.arFrame)
JobDetailsDomain.QR_JOB -> qrRecognizeViewModel.recognizeQrCode(sceneView.arFrame)
}
}
}
}
private fun listenToDetectorsAnswers(jobType: String) {
val answer = jobDetailsDomain.answer
when (jobType) {
JobDetailsDomain.FIND_OBJECT_JOB -> listenToObjectRecognizeResults(answer)
JobDetailsDomain.TEXT_ANSWER_JOB -> listenToTextRecognizeResults(answer)
JobDetailsDomain.QR_JOB -> listenToQrScanResults(answer)
}
}
private fun listenToObjectRecognizeResults(answer: String) {
objectRecognizeViewModel.debugTestModel()
objectRecognizeViewModel.listenToResults().listen(viewLifecycleOwner) { results ->
results.forEach { result ->
gamePlayViewModel.showSuccessDialog(result.toJson())
if (result.title.toLowerCase(Locale.getDefault())
.contains(answer.toLowerCase(Locale.getDefault()))) {
gamePlayViewModel.submitAnswer(SubmitAnswerUseCase.getFindObjectAnswer(
isCorrect = true,
score = result.confidence.toDouble()
))
}
}
}
}
private fun listenToTextRecognizeResults(answer: String) {
textRecognizerViewModel.listenToResults().listen(viewLifecycleOwner) { result ->
gamePlayViewModel.showSuccessDialog(result.toJson())
if (result.toLowerCase(Locale.getDefault()).contains(answer.toLowerCase(Locale.getDefault()))) {
gamePlayViewModel.submitAnswer(SubmitAnswerUseCase.getTextRecognizeAnswer(
text = answer
))
}
}
}
private fun listenToQrScanResults(answer: String) {
qrRecognizeViewModel.listenToResults().listen(viewLifecycleOwner) { result ->
gamePlayViewModel.showSuccessDialog(result.toJson())
if (result.contains(answer)) {
gamePlayViewModel.submitAnswer(SubmitAnswerUseCase.getQrScanAnswer(
qrCodeText = answer))
}
}
}
private fun checkTrackingState(trackingState: TrackingState) {
if (currentTrackingState != trackingState) {
currentTrackingState = trackingState
when (currentTrackingState) {
TrackingState.PAUSED,
TrackingState.STOPPED
-> locationArScene.clearMarkers()
TrackingState.TRACKING -> {
arViewModel.getCurrentNavigationMarker().value?.let { markers ->
locationArScene.clearMarkers()
markers.forEach { marker ->
renderNavigationMarker(marker)
}
}
}
}
}
}
private fun updateArMarkersUi() {
if (arViewModel.shouldUpdateRenderable()) {
val markers = arViewModel.getCurrentNavigationMarker().value
if (markers.isNullOrEmpty()) {
return
}
markers.forEach { marker ->
setMarkerUi(marker)
}
}
}
private fun setLocationListener() {
arViewModel.getLocationLiveData().listen(viewLifecycleOwner) { location ->
if (::locationArScene.isInitialized)
locationArScene.onLocationChanged(
newLocation = GlobalPosition(location.latitude, location.longitude))
}
}
private fun setBearingListener() {
arViewModel.getBearing().listen(viewLifecycleOwner) { bearing ->
north.rotation = bearing.azimuth
if (::locationArScene.isInitialized)
locationArScene.onBearingChanged(bearing.azimuth)
}
}
private fun renderNavigationMarker(navigationMarker: NavigationMarker) {
TimberLog.d("load view renderable: %s ", navigationMarker.title)
arViewModel.loadViewRenderable(getNavigationActivity(), navigationMarker.viewId, { renderable ->
val marker = arViewModel.renderArObject(
navigationMarker.latitude,
navigationMarker.longitude,
navigationMarker.height,
renderable)
navigationMarker.viewRenderable = renderable
locationArScene.addMarker(marker)
setMarkerUi(navigationMarker)
}, { error -> TimberLog.e("Error while loading view renderables: %s", error.message) })
}
private fun setMarkerUi(navigationMarker: NavigationMarker) {
when (navigationMarker.type) {
is Type.RouteStep -> {
navigationMarker.viewRenderable?.view?.findViewById<TextView>(R.id.title).apply {
this?.text = navigationMarker.title
}
navigationMarker.viewRenderable?.view?.findViewById<TextView>(R.id.distance).apply {
this?.text = arViewModel.getDistanceToMarker(navigationMarker.latitude, navigationMarker.longitude)
}
}
}
}
private fun checkPermissions() {
Dexter.withContext(getNavigationActivity())
.withPermissions(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
TimberLog.d("Permissions granted.")
if (report.areAllPermissionsGranted()) {
arLifeCycle = ArSceneLifecycleUtil(getNavigationActivity(), sceneView)
locationArScene = LocationArScene(sceneView).apply {
startLocationScene()
}
setLocationListener()
setBearingListener()
setOnUpdateListener()
listenToMarkersChange()
}
}
override fun onPermissionRationaleShouldBeShown(permissions: List<PermissionRequest?>?, token: PermissionToken?) {
}
}).check()
}
private fun listenToMarkersChange() {
arViewModel.getCurrentNavigationMarker().listen(viewLifecycleOwner) { markers ->
locationArScene.clearMarkers()
markers.forEach { marker ->
renderNavigationMarker(marker)
}
}
}
private fun setOnUpdateListener() = sceneView
.scene
.addOnUpdateListener(this)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment