Angle update

This commit is contained in:
2026-08-12 12:46:33 +08:00
parent 18fe809d4a
commit e05aca3312
10 changed files with 244 additions and 74 deletions
@@ -33,16 +33,12 @@ import java.util.concurrent.Executors
*/
class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
/** Which button a recording in progress was started from. */
private enum class RecordMode { VIDEO, POSE }
private lateinit var binding: ActivityBowlingCameraBinding
private val viewModel: CameraViewModel by viewModels()
private lateinit var cameraExecutor: ExecutorService
private lateinit var cameraXController: CameraXController
private var lensFacing = CameraSelector.LENS_FACING_BACK
private var activeRecordMode: RecordMode? = null
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { _ ->
@@ -67,8 +63,8 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
binding.btnGrantPermissions.setOnClickListener {
permissionLauncher.launch(CameraPermissions.REQUIRED)
}
binding.btnRecordVideo.setOnClickListener { onRecordClicked(RecordMode.VIDEO) }
binding.btnRecordWithPose.setOnClickListener { onRecordClicked(RecordMode.POSE) }
binding.btnRecord.setOnClickListener { onRecordClicked() }
binding.switchPose.setOnCheckedChangeListener { _, isChecked -> viewModel.onPoseToggled(isChecked) }
binding.btnSwitchCamera.setOnClickListener { onSwitchCameraClicked() }
binding.btnBack.setOnClickListener { finish() }
@@ -93,17 +89,14 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
)
}
private fun onRecordClicked(mode: RecordMode) {
private fun onRecordClicked() {
if (cameraXController.isRecording) {
cameraXController.stopRecording()
} else {
activeRecordMode = mode
val withPose = mode == RecordMode.POSE
cameraXController.setPoseDetectionEnabled(withPose)
if (!withPose) {
// Clear any skeleton left over from a previous "with pose" recording.
binding.poseOverlay.clear()
}
// Pose mode for this recording is whatever switch_pose is
// already set to -- see the poseEnabled collector in
// observeViewModel(), which keeps cameraXController's live pose
// state in sync with the toggle as it's flipped.
viewModel.onRecordingStarting()
cameraXController.startRecording()
}
@@ -133,6 +126,19 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
launch {
viewModel.recordingState.collect { state -> renderRecordingState(state) }
}
launch {
viewModel.poseEnabled.collect { enabled ->
// Also drives the switch's checked state (idempotent
// if the user just flipped it themselves), so it
// reflects viewModel state after an Activity
// recreation, e.g. on rotation.
if (binding.switchPose.isChecked != enabled) {
binding.switchPose.isChecked = enabled
}
cameraXController.setPoseDetectionEnabled(enabled)
if (!enabled) binding.poseOverlay.clear()
}
}
launch {
viewModel.errorEvents.collect { message ->
Toast.makeText(this@BowlingCameraActivity, message, Toast.LENGTH_LONG).show()
@@ -157,28 +163,23 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
}
when (state) {
is CameraViewModel.RecordingState.Idle -> {
activeRecordMode = null
binding.layoutRecordingIndicator.visibility = View.GONE
binding.btnRecordVideo.isEnabled = true
binding.btnRecordVideo.setText(R.string.record_video)
binding.btnRecordWithPose.isEnabled = true
binding.btnRecordWithPose.setText(R.string.record_with_pose)
binding.btnRecord.isEnabled = true
binding.btnRecord.setText(R.string.record)
// Pose mode can only be changed between recordings, not
// mid-flight -- see setPoseDetectionEnabled()'s doc comment.
binding.switchPose.isEnabled = true
}
is CameraViewModel.RecordingState.Starting -> {
// Neither button switches mode mid-flight, and neither can
// stop a recording that hasn't started yet.
binding.btnRecordVideo.isEnabled = false
binding.btnRecordWithPose.isEnabled = false
// Can't stop a recording that hasn't started yet, and pose
// mode for it is already locked in.
binding.btnRecord.isEnabled = false
binding.switchPose.isEnabled = false
}
is CameraViewModel.RecordingState.Recording -> {
// Only the button that started this recording stays enabled
// (now as the stop trigger); the other is disabled rather
// than switching mode mid-recording.
val videoActive = activeRecordMode == RecordMode.VIDEO
binding.btnRecordVideo.isEnabled = videoActive
binding.btnRecordVideo.setText(if (videoActive) R.string.stop_recording else R.string.record_video)
binding.btnRecordWithPose.isEnabled = !videoActive
binding.btnRecordWithPose.setText(if (!videoActive) R.string.stop_recording else R.string.record_with_pose)
binding.btnRecord.isEnabled = true
binding.btnRecord.setText(R.string.stop_recording)
binding.switchPose.isEnabled = false
binding.layoutRecordingIndicator.visibility = View.VISIBLE
val minutes = state.elapsedSeconds / 60
val seconds = state.elapsedSeconds % 60
@@ -232,6 +233,7 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
// (see PoseAnalyzer) deliver on the main thread by default even
// though inference itself runs on the background camera executor.
binding.poseOverlay.update(result)
viewModel.onPoseAnglesUpdated(result.angles)
}
override fun onDestroy() {