Files
PinPoint/app/src/main/res/layout-land/activity_bowling_camera.xml
T

360 lines
15 KiB
XML
Raw Normal View History

2026-08-11 19:46:54 +08:00
<?xml version="1.0" encoding="utf-8"?>
<!--
Landscape button arrangement: with the screen wide and short, a bottom bar
(see the portrait default in res/layout/) would leave little room for the
preview and put the record button awkwardly close to the edge, so it moves
to a vertically-centered side column instead. Same view IDs as the
portrait layout so BowlingCameraActivity's view-binding code needs no
orientation-specific logic - Android just swaps which XML gets inflated.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
tools:context=".bowling.BowlingCameraActivity">
<androidx.camera.view.PreviewView
android:id="@+id/camera_preview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.example.jnicpp.bowling.PoseOverlayView
android:id="@+id/pose_overlay"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/camera_preview"
app:layout_constraintBottom_toBottomOf="@id/camera_preview"
app:layout_constraintStart_toStartOf="@id/camera_preview"
app:layout_constraintEnd_toEndOf="@id/camera_preview" />
<!--
Recording indicator: red dot + elapsed timer, only visible while
recording. Anchored below btn_back (rather than parent's top) so the
two never overlap - btn_back is declared later in this file so it
draws on top, but ConstraintLayout resolves constraint references
regardless of declaration order, so this forward reference is fine.
-->
<LinearLayout
android:id="@+id/layout_recording_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/overlay_scrim"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/btn_back"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp">
<View
android:id="@+id/view_recording_dot"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/shape_recording_dot" />
<TextView
android:id="@+id/text_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/recording_timer_placeholder"
android:textColor="@color/white"
android:textSize="16sp"
android:fontFamily="monospace" />
</LinearLayout>
<!-- Live step counter: see the portrait layout's copy of this view for
the full rationale. Same IDs, centered top here too since
landscape's top edge is otherwise clear (buttons moved to the side
column below). -->
<LinearLayout
android:id="@+id/card_step_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="72dp"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/shape_step_counter_card"
android:paddingHorizontal="24dp"
android:paddingVertical="8dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
2026-08-14 18:16:55 +08:00
<TextView
android:id="@+id/text_step_count_big"
2026-08-14 18:16:55 +08:00
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_count_big_placeholder"
2026-08-14 18:16:55 +08:00
android:textColor="@color/white"
android:textSize="40sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_counter_label"
android:textColor="@color/step_counter_accent"
android:textSize="12sp"
android:letterSpacing="0.15"
2026-08-14 18:16:55 +08:00
android:textStyle="bold" />
2026-08-11 19:46:54 +08:00
</LinearLayout>
<!-- Hold-to-reset gesture: see the portrait layout's copy for the full
rationale. Bottom-center of the whole screen here instead of above
switch_pose, since landscape's buttons sit in a side column rather
than a bottom bar. -->
<LinearLayout
android:id="@+id/layout_reset_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/overlay_scrim"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress_hand_raise"
android:layout_width="20dp"
android:layout_height="20dp"
android:indeterminate="false"
android:max="100"
android:progress="0"
app:indicatorSize="20dp"
app:trackThickness="3dp"
app:indicatorColor="@color/step_counter_accent"
app:trackColor="@color/white" />
<TextView
android:id="@+id/text_reset_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/reset_hint_idle"
android:textColor="@color/white"
android:textSize="13sp" />
</LinearLayout>
2026-09-05 22:33:13 +08:00
<!-- Delivery-phase feedback (e.g. "Starting stance"), separate from the
step counter above - see CameraViewModel#posePhase. Shown whenever
pose detection is on, live preview or recording, unlike the step
counter which is recording-only. -->
<TextView
android:id="@+id/text_pose_feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/overlay_scrim"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:visibility="visible"
tools:text="Get into starting stance"
app:layout_constraintTop_toBottomOf="@id/btn_back"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" />
<!-- Raw torso/knee/elbow angle readout backing text_pose_feedback above
- see CameraViewModel#poseMetrics. Stacked directly below it. -->
<TextView
android:id="@+id/text_pose_metrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/overlay_scrim"
android:paddingHorizontal="12dp"
android:paddingVertical="4dp"
android:textColor="@color/white"
android:textSize="14sp"
android:fontFamily="monospace"
android:visibility="gone"
tools:visibility="visible"
tools:text="Torso 12° · Knee L168° R170° · Elbow L85° R90°"
app:layout_constraintTop_toBottomOf="@id/text_pose_feedback"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp" />
<!-- Groups the step/final-position banner and the per-step form cue into
one centered vertical stack, anchored below the top corner buttons.
A LinearLayout (rather than each TextView chained to the other via
ConstraintLayout's toBottomOf) so a GONE child collapses cleanly.
Chaining directly had text_pose_feedback render at text_final_position's
collapsed (zero-height) position whenever the latter was hidden,
landing on top of the recording indicator instead of staying put. -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
app:layout_constraintTop_toBottomOf="@id/btn_switch_camera"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp">
<!-- Live "you're in your final position" banner, shown once the 5th
(final) foot-plant of the current attempt is detected (see
BowlingCameraActivity's stepEvents collector), cleared
automatically whenever the step count resets for the next attempt. -->
<TextView
android:id="@+id/text_final_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/overlay_scrim"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:text="@string/final_position_reached"
android:textColor="@color/final_position_highlight"
android:textSize="22sp"
android:textStyle="bold"
android:visibility="gone"
tools:visibility="visible" />
<!-- Live per-step form cue from PoseStageAdvisor (e.g. "Good
push-away", "Bend your sliding knee more"), see
CameraViewModel.poseStageFeedback. -->
<TextView
android:id="@+id/text_pose_feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/overlay_scrim"
android:paddingHorizontal="16dp"
android:paddingVertical="6dp"
android:textColor="@color/white"
android:textSize="16sp"
android:visibility="gone"
tools:text="Good push-away"
tools:visibility="visible" />
</LinearLayout>
2026-08-11 19:46:54 +08:00
<!--
2026-08-12 12:46:33 +08:00
Mirrored onto the start edge at the same vertical center as btn_record
on the end edge. Live pose overlay + baked-in-recording toggle; only
togglable while not recording (see
BowlingCameraActivity#renderRecordingState) since the recording
pipeline picks its pose mode once at start.
2026-08-11 19:46:54 +08:00
-->
2026-08-12 12:46:33 +08:00
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_pose"
2026-08-11 19:46:54 +08:00
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
2026-08-12 12:46:33 +08:00
android:text="@string/toggle_pose"
android:textColor="@color/white"
app:layout_constraintTop_toTopOf="@id/btn_record"
app:layout_constraintBottom_toBottomOf="@id/btn_record"
2026-08-11 19:46:54 +08:00
app:layout_constraintStart_toStartOf="parent" />
2026-08-12 12:46:33 +08:00
<!-- Side column instead of a bottom bar: vertically centered, hugging the end edge. -->
2026-08-11 19:46:54 +08:00
<Button
2026-08-12 12:46:33 +08:00
android:id="@+id/btn_record"
2026-08-11 19:46:54 +08:00
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
2026-08-12 12:46:33 +08:00
android:text="@string/record"
2026-08-11 19:46:54 +08:00
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
2026-08-12 12:46:33 +08:00
<!-- Above the record button in the same side column, rather than the top corner. -->
2026-08-11 19:46:54 +08:00
<Button
android:id="@+id/btn_switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/switch_camera"
2026-08-12 12:46:33 +08:00
app:layout_constraintBottom_toTopOf="@id/btn_record"
app:layout_constraintEnd_toEndOf="@id/btn_record" />
2026-08-11 19:46:54 +08:00
<!-- Opens ParameterEditorActivity: see the portrait layout's copy
for the full rationale. Above btn_switch_camera in the same
column, only shown while Idle. -->
<Button
android:id="@+id/btn_editor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/editor_button"
app:layout_constraintBottom_toTopOf="@id/btn_switch_camera"
app:layout_constraintEnd_toEndOf="@id/btn_record" />
2026-09-06 22:31:36 +08:00
<!-- to only show when recording-->
<Button
android:id="@+id/btn_show_step"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
2026-09-08 10:31:35 +08:00
android:text="@string/pose_phase_waiting"
2026-09-06 22:31:36 +08:00
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
2026-08-11 19:46:54 +08:00
<!-- Shown instead of the camera UI when CAMERA/RECORD_AUDIO aren't granted -->
<LinearLayout
android:id="@+id/layout_permission_rationale"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp"
android:background="@color/black"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/text_permission_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/permission_rationale_message"
android:textColor="@color/white"
android:textSize="16sp"
android:gravity="center"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btn_grant_permissions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grant_permissions" />
</LinearLayout>
<!-- Declared last so it draws above the permission rationale screen too,
keeping a way back out of this Activity available in every state. -->
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:text="@string/back"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>