/** * @file StepCounterUiController.kt * @brief Rendering for the live step-counter card. */ package com.example.jnicpp.bowling import android.view.View import android.widget.TextView import androidx.core.content.ContextCompat import com.example.jnicpp.R /** * @brief Owns rendering for [BowlingCameraActivity]'s step-counter card. * * @param cardStepCounter The step-counter card container view. * @param textStepCountBig The large step-count number TextView. * @param textStepCounterLabel The small "STEPS" label below the count, * repurposed to flag when the latest step wasn't corroborated by * [PosePhaseDetector] -- see [renderStepCount]'s `latestPoseConfirmed`. */ class StepCounterUiController( private val cardStepCounter: View, private val textStepCountBig: TextView, private val textStepCounterLabel: TextView, ) { // Last step count rendered, so pulse() in renderStepCount only plays // when a new step actually pushed the count up. private var lastRenderedStepCount = 0 /** * @brief Shows or hides the step counter card. * @param visible true to reveal (recording in progress), false to hide. */ fun setVisible(visible: Boolean) { cardStepCounter.visibility = if (visible) View.VISIBLE else View.GONE } /** @brief Clears pulse-tracking state; call whenever a new recording starts. */ fun resetTracking() { lastRenderedStepCount = 0 } /** * @brief Renders the current step count, pulsing the card if a new step was just confirmed. * * Never withholds or delays a step because of [latestPoseConfirmed] -- * the ankle-peak count is always trusted (see [StepEvent.poseConfirmed]'s * doc for why); this only swaps the small label below the number to flag * a disagreement for whoever's testing/tuning detection to notice. * * @param stepCount Total steps counted so far in the current attempt. * @param latestPoseConfirmed Whether the most recently counted step (if * any) was corroborated by [PosePhaseDetector] at the time it was * counted; ignored when [stepCount] is 0. */ fun renderStepCount(stepCount: Int, latestPoseConfirmed: Boolean = true) { textStepCountBig.text = stepCount.toString() if (stepCount > lastRenderedStepCount) { pulse() } lastRenderedStepCount = stepCount val flagged = stepCount > 0 && !latestPoseConfirmed textStepCounterLabel.text = textStepCounterLabel.context.getString( if (flagged) R.string.step_counter_label_unconfirmed else R.string.step_counter_label ) textStepCounterLabel.setTextColor( ContextCompat.getColor( textStepCounterLabel.context, if (flagged) R.color.recording_red else R.color.step_counter_accent, ) ) } /** @brief Briefly scales the step counter up and back down, drawing the eye to a newly confirmed step. */ private fun pulse() { cardStepCounter.animate() .scaleX(1.15f).scaleY(1.15f) .setDuration(80) .withEndAction { cardStepCounter.animate().scaleX(1f).scaleY(1f).setDuration(120).start() } .start() } }