visual enhancements and documentation changes.
This commit is contained in:
@@ -7,13 +7,13 @@
|
|||||||
- **Run Unit Tests**: `./gradlew test` (if applicable)
|
- **Run Unit Tests**: `./gradlew test` (if applicable)
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
- **Language**: Kotlin 1.9+
|
- **Language**: Kotlin 1.9.0
|
||||||
- **UI Framework**: Traditional Android View system (XML layouts), **not** Jetpack Compose.
|
- **UI Framework**: Traditional Android View system (XML layouts), **not** Jetpack Compose.
|
||||||
- **Naming Conventions**:
|
- **Naming Conventions**:
|
||||||
- Classes: `PascalCase` (e.g., `CountdownDialog`)
|
- Classes: `PascalCase` (e.g., `CountdownDialog`)
|
||||||
- Functions/Variables: `camelCase` (e.g., `startTimer()`)
|
- Functions/Variables: `camelCase` (e.g., `startTimer()`)
|
||||||
- Layout Files: `snake_case` (e.g., `activity_main.xml`)
|
- Layout Files: `snake_case` (e.g., `activity_main.xml`)
|
||||||
- Resource IDs: `snake_case` (e.g., `btn_start`)
|
- Resource IDs: `camelCase` (e.g., `btnStart`, `cvCountdown`)
|
||||||
- **Android Patterns**: Use `findViewById` for view binding in this project to maintain consistency with existing simple architecture.
|
- **Android Patterns**: Use `findViewById` for view binding in this project to maintain consistency with existing simple architecture.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
## Critical Files Reference
|
## Critical Files Reference
|
||||||
- `app/src/main/java/com/example/countdowntimer/MainActivity.kt`: Entry point and trigger for the timer.
|
- `app/src/main/java/com/example/countdowntimer/MainActivity.kt`: Entry point and trigger for the timer.
|
||||||
- `app/src/main/java/com/example/countdowntimer/CountdownDialog.kt`: Implementation of the countdown logic and `ToneGenerator` trigger.
|
- `app/src/main/java/com/example/countdowntimer/CountdownDialog.kt`: Implementation of the countdown logic and `ToneGenerator` trigger.
|
||||||
|
- `app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt`: Custom view for the circular progress ring and countdown text.
|
||||||
- `app/src/main/res/layout/activity_main.xml`: Main screen layout.
|
- `app/src/main/res/layout/activity_main.xml`: Main screen layout.
|
||||||
- `app/src/main/res/layout/dialog_countdown.xml`: Timer dialog layout.
|
- `app/src/main/res/layout/dialog_countdown.xml`: Timer dialog layout.
|
||||||
- `app/build.gradle`: Build configuration, SDK versions (minSdk 24, targetSdk 34), and dependencies.
|
- `app/build.gradle`: Build configuration, SDK versions (minSdk 24, targetSdk 34), and dependencies.
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
A minimal Android application that demonstrates a simple countdown functionality with an audible alert.
|
A minimal Android application that demonstrates a simple countdown functionality with an audible alert.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
The Countdown Timer app is a lightweight utility that launches a modal dialog to count down from 10 seconds. Once the timer reaches zero, the app plays a system error tone and closes the dialog. This project serves as a basic example of using `CountDownTimer`, `ToneGenerator`, and custom `Dialog` implementation in Android.
|
The Countdown Timer app is a lightweight utility that launches a modal dialog to count down from a user-selected duration (5s, 10s, 15s, or 30s). Once the timer reaches zero, the app plays a confirmation tone and vibration, then shows a dismiss button. This project serves as a basic example of using `CountDownTimer`, `ToneGenerator`, and custom `Dialog` implementation in Android.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
- **Single-Action Interface**: A simple main screen with a start button.
|
- **Selectable Duration**: A main screen with a duration picker (chip group) and a start button.
|
||||||
- **Modal Countdown**: A non-cancelable dialog that prevents user interference during the countdown.
|
- **Modal Countdown**: A full-screen dialog with circular progress ring, stop/restart buttons, and a confirmation tone during the final 3 seconds.
|
||||||
- **Audible Alert**: Integration with Android's `ToneGenerator` to play `TONE_SUP_ERROR` upon completion.
|
- **Haptic Feedback**: Vibration on the last 3 seconds and on completion (configurable via shared preferences).
|
||||||
- **Real-time Updates**: UI updates every second to reflect the remaining time.
|
- **Real-time Updates**: UI updates every second to reflect the remaining time.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@@ -25,19 +25,46 @@ To build the debug APK from the command line:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Running the App
|
### Running the App
|
||||||
|
|
||||||
|
#### Via Android Studio
|
||||||
1. Open the project in Android Studio.
|
1. Open the project in Android Studio.
|
||||||
2. Select your target device/emulator.
|
2. Select your target device/emulator.
|
||||||
3. Click the **Run** button or press `Shift + F10`.
|
3. Click the **Run** button or press `Shift + F10`.
|
||||||
|
|
||||||
|
#### Via ADB
|
||||||
|
First, ensure your device or emulator is connected (`adb devices`). Then install the debug APK and launch the app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install the debug APK
|
||||||
|
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
|
||||||
|
# Launch the app
|
||||||
|
adb shell am start -n com.example.countdowntimer.debug/com.example.countdowntimer.MainActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Taking a Screenshot via ADB
|
||||||
|
To capture a screenshot of the app from your host machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Take the screenshot on the device
|
||||||
|
adb shell screencap -p /sdcard/screenshot.png
|
||||||
|
|
||||||
|
# Pull it to your host machine
|
||||||
|
adb pull /sdcard/screenshot.png screenshots/
|
||||||
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
```text
|
```text
|
||||||
app/
|
app/
|
||||||
├── src/
|
├── src/
|
||||||
│ └── main/
|
│ └── main/
|
||||||
│ ├── java/com/example/countdowntimer/
|
│ ├── java/com/example/countdowntimer/
|
||||||
│ │ ├── MainActivity.kt # Main entry point; launches the timer
|
│ │ ├── MainActivity.kt # Main entry point; duration picker and timer trigger
|
||||||
│ │ └── CountdownDialog.kt # Logic for the 10s countdown and tone
|
│ │ ├── CountdownDialog.kt # Countdown logic, tone, and vibration
|
||||||
│ ├── res/ # UI layouts and resources
|
│ │ └── CountdownCanvasView.kt # Custom circular progress ring view
|
||||||
│ └── AndroidManifest.xml # Application manifest
|
│ ├── res/layout/ # UI layouts
|
||||||
└── build.gradle # App-level build configuration
|
│ │ ├── activity_main.xml
|
||||||
|
│ │ └── dialog_countdown.xml
|
||||||
|
│ └── AndroidManifest.xml # Application manifest
|
||||||
|
└── build.gradle # App-level build configuration
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="120dp"
|
||||||
|
android:height="120dp"
|
||||||
|
android:viewportWidth="120"
|
||||||
|
android:viewportHeight="120"
|
||||||
|
android:tint="@android:color/white">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,20v80l60,-40z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="64dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="64dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M655,880L513,738L569,682L654,767L824,597L880,654L655,880ZM480,490L480,490L480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490Q480,490 480,490ZM412,750L501,839Q496,839 490.5,839.5Q485,840 480,840Q346,840 253,747Q160,654 160,520Q160,391 246.5,275Q333,159 480,80L480,212Q480,246 503.5,269Q527,292 561,292Q579,292 594.5,284.5Q610,277 622,262L640,240Q714,282 757,357Q800,432 800,520L720,520Q720,495 715,471.5Q710,448 701,425.5Q692,403 678.5,383Q665,363 648,346Q628,359 606,365.5Q584,372 561,372Q499,372 453.5,331Q408,290 401,230L401,230Q323,296 281.5,370.5Q240,445 240,520Q240,602 288.5,665Q337,728 412,750Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintWidth_percent="0.7">
|
app:layout_constraintWidth_percent=".85">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/instructionsLayout"
|
android:id="@+id/instructionsLayout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@@ -68,11 +68,13 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:src="@drawable/gas_gauge_full"
|
android:src="@drawable/outline_fire_check_24"
|
||||||
app:tint="@color/color_icon_tint" />
|
app:tint="@color/color_icon_tint" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -87,11 +89,13 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:src="@drawable/chair"
|
android:src="@drawable/outline_air_24"
|
||||||
app:tint="@color/color_icon_tint" />
|
app:tint="@color/color_icon_tint" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -106,11 +110,13 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:src="@drawable/outline_air_24"
|
android:src="@drawable/ic_play_arrow"
|
||||||
app:tint="@color/color_icon_tint" />
|
app:tint="@color/color_icon_tint" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -163,18 +169,18 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg"
|
android:text="5s"
|
||||||
android:text="5s" />
|
app:chipBackgroundColor="@color/chip_duration_bg" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/chip10s"
|
android:id="@+id/chip10s"
|
||||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
|
||||||
android:checked="true"
|
android:checked="true"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg"
|
android:minWidth="56dp"
|
||||||
android:text="10s" />
|
android:text="10s"
|
||||||
|
app:chipBackgroundColor="@color/chip_duration_bg" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/chip15s"
|
android:id="@+id/chip15s"
|
||||||
@@ -182,8 +188,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg"
|
android:text="15s"
|
||||||
android:text="15s" />
|
app:chipBackgroundColor="@color/chip_duration_bg" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/chip30s"
|
android:id="@+id/chip30s"
|
||||||
@@ -191,24 +197,24 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg"
|
android:text="30s"
|
||||||
android:text="30s" />
|
app:chipBackgroundColor="@color/chip_duration_bg" />
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</com.google.android.material.chip.ChipGroup>
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnStart"
|
android:id="@+id/btnStart"
|
||||||
android:layout_width="120dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="120dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:background="@drawable/btn_start_selector"
|
android:background="@drawable/btn_start_selector"
|
||||||
android:drawableTop="@drawable/ic_play_arrow"
|
android:contentDescription="Start"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="16dp"
|
android:padding="24dp"
|
||||||
android:text="@string/start_timer"
|
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
app:icon="@drawable/ic_play_arrow_large"
|
||||||
|
app:iconSize="56dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/hsvDurationOptions" />
|
app:layout_constraintTop_toBottomOf="@+id/hsvDurationOptions" />
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<string name="countdown_text">!!! HOLD !!!</string>
|
<string name="countdown_text">!!! HOLD !!!</string>
|
||||||
<string name="timer_zero">EXHALE!</string>
|
<string name="timer_zero">EXHALE!</string>
|
||||||
<string name="instruction_title">BONG HIT TIMER!</string>
|
<string name="instruction_title">BONG HIT TIMER!</string>
|
||||||
<string name="instruction_volume">Ensure your bong is loaded</string>
|
<string name="instruction_volume">Light your bowl</string>
|
||||||
<string name="instruction_position">Find a comfortable position</string>
|
<string name="instruction_position">Take your hit</string>
|
||||||
<string name="instruction_distractions">Light your bowl.\nTake your hit.\nPress Start.</string>
|
<string name="instruction_distractions">Press Start to time how long to hold it!</string>
|
||||||
<string name="pick_duration">Pick Duration</string>
|
<string name="pick_duration">Pick Duration</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="vibration">Vibration</string>
|
<string name="vibration">Vibration</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user