Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97b01437b2 | ||
|
|
f87d23a922 |
@@ -3,6 +3,9 @@ package com.example.countdowntimer
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
import android.os.VibratorManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
@@ -21,7 +24,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private lateinit var btnDurationLabel: Button
|
private lateinit var btnDurationLabel: Button
|
||||||
private lateinit var hsvDurationOptions: View
|
private lateinit var hsvDurationOptions: View
|
||||||
private lateinit var chipGroup: ChipGroup
|
private lateinit var chipGroup: ChipGroup
|
||||||
|
private lateinit var vibrator: Vibrator
|
||||||
private var selectedDuration: Long = DEFAULT_DURATION
|
private var selectedDuration: Long = DEFAULT_DURATION
|
||||||
|
private var lastSelectedChipId: Int = -1
|
||||||
|
|
||||||
private fun durationToChipId(duration: Long): Int = when (duration) {
|
private fun durationToChipId(duration: Long): Int = when (duration) {
|
||||||
5000L -> R.id.chip5s
|
5000L -> R.id.chip5s
|
||||||
@@ -42,6 +47,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
btnDurationLabel.text = "${selectedDuration / 1000}s"
|
btnDurationLabel.text = "${selectedDuration / 1000}s"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onChipClicked(chipId: Int) {
|
||||||
|
if (chipId == lastSelectedChipId) {
|
||||||
|
// Re-selection: provide feedback so user knows the selection is registered
|
||||||
|
vibrator.vibrate(VibrationEffect.createOneShot(15, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||||
|
val originalText = btnDurationLabel.text
|
||||||
|
btnDurationLabel.text = "${selectedDuration / 1000}s!"
|
||||||
|
btnDurationLabel.postDelayed({ btnDurationLabel.text = originalText }, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
@@ -52,6 +67,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
btnDurationLabel = findViewById(R.id.btnDurationLabel)
|
btnDurationLabel = findViewById(R.id.btnDurationLabel)
|
||||||
hsvDurationOptions = findViewById(R.id.hsvDurationOptions)
|
hsvDurationOptions = findViewById(R.id.hsvDurationOptions)
|
||||||
chipGroup = findViewById(R.id.chipGroup)
|
chipGroup = findViewById(R.id.chipGroup)
|
||||||
|
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
|
vibrator = vibratorManager.defaultVibrator
|
||||||
|
|
||||||
updateDurationLabel()
|
updateDurationLabel()
|
||||||
|
|
||||||
@@ -72,15 +89,27 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chipGroup.setOnCheckedChangeListener { _, checkedId ->
|
chipGroup.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||||
|
val checkedId = if (checkedIds.size > 0) checkedIds[0].toInt() else -1
|
||||||
if (checkedId != -1) {
|
if (checkedId != -1) {
|
||||||
|
if (checkedId != lastSelectedChipId) {
|
||||||
|
// New selection: update duration, save, and refresh label
|
||||||
selectedDuration = chipIdToDuration(checkedId)
|
selectedDuration = chipIdToDuration(checkedId)
|
||||||
prefs.edit().putLong(KEY_SELECTED_DURATION, selectedDuration).apply()
|
prefs.edit().putLong(KEY_SELECTED_DURATION, selectedDuration).apply()
|
||||||
updateDurationLabel()
|
updateDurationLabel()
|
||||||
|
lastSelectedChipId = checkedId
|
||||||
|
}
|
||||||
|
// Always close the panel on any selection
|
||||||
hsvDurationOptions.visibility = View.GONE
|
hsvDurationOptions.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set individual click listeners to detect re-selection
|
||||||
|
findViewById<View>(R.id.chip5s).setOnClickListener { onChipClicked(R.id.chip5s) }
|
||||||
|
findViewById<View>(R.id.chip10s).setOnClickListener { onChipClicked(R.id.chip10s) }
|
||||||
|
findViewById<View>(R.id.chip15s).setOnClickListener { onChipClicked(R.id.chip15s) }
|
||||||
|
findViewById<View>(R.id.chip30s).setOnClickListener { onChipClicked(R.id.chip30s) }
|
||||||
|
|
||||||
findViewById<View>(R.id.btnStart).setOnClickListener {
|
findViewById<View>(R.id.btnStart).setOnClickListener {
|
||||||
CountdownDialog(this, selectedDuration, prefs).show()
|
CountdownDialog(this, selectedDuration, prefs).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:state_checked="true" android:color="#81C784" />
|
<item android:state_checked="true" android:color="#2E7D32" />
|
||||||
<item android:color="#E8E8E8" />
|
<item android:color="#233024" />
|
||||||
</selector>
|
</selector>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true" android:color="#C8E6C9" />
|
||||||
|
<item android:color="#2E7D32" />
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true" android:color="#FFFFFF" />
|
||||||
|
<item android:color="#C8E6C9" />
|
||||||
|
</selector>
|
||||||
@@ -170,17 +170,22 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
android:text="5s"
|
android:text="5s"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg" />
|
app:chipBackgroundColor="@color/chip_duration_bg"
|
||||||
|
app:chipStrokeColor="@color/chip_duration_stroke"
|
||||||
|
app:chipStrokeWidth="2dp"
|
||||||
|
android:textColor="@color/chip_duration_text" />
|
||||||
|
|
||||||
<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:checked="true"
|
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
android:text="10s"
|
android:text="10s"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg" />
|
app:chipBackgroundColor="@color/chip_duration_bg"
|
||||||
|
app:chipStrokeColor="@color/chip_duration_stroke"
|
||||||
|
app:chipStrokeWidth="2dp"
|
||||||
|
android:textColor="@color/chip_duration_text" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/chip15s"
|
android:id="@+id/chip15s"
|
||||||
@@ -189,7 +194,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
android:text="15s"
|
android:text="15s"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg" />
|
app:chipBackgroundColor="@color/chip_duration_bg"
|
||||||
|
app:chipStrokeColor="@color/chip_duration_stroke"
|
||||||
|
app:chipStrokeWidth="2dp"
|
||||||
|
android:textColor="@color/chip_duration_text" />
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/chip30s"
|
android:id="@+id/chip30s"
|
||||||
@@ -198,7 +206,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp"
|
android:minWidth="56dp"
|
||||||
android:text="30s"
|
android:text="30s"
|
||||||
app:chipBackgroundColor="@color/chip_duration_bg" />
|
app:chipBackgroundColor="@color/chip_duration_bg"
|
||||||
|
app:chipStrokeColor="@color/chip_duration_stroke"
|
||||||
|
app:chipStrokeWidth="2dp"
|
||||||
|
android:textColor="@color/chip_duration_text" />
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</com.google.android.material.chip.ChipGroup>
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
# Goal: Redesign all buttons into bold, square, visually striking interactive elements
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Replace every basic/rectangular button in the app with larger, square-aspect-ratio buttons that have eye-catching visual design. The goal is to transform the UI from a generic Material 3 look into a distinctive, personality-rich interface that matches the playful "BONG HIT TIMER" brand.
|
|
||||||
|
|
||||||
## Current State
|
|
||||||
The app uses 4 visible buttons across 2 screens:
|
|
||||||
|
|
||||||
**MainActivity (home screen):**
|
|
||||||
- `btnDurationLabel` — outlined button showing selected duration (e.g. "10s") with a dropdown arrow. Uses `Widget.Material3.Button.OutlinedButton` with 20dp corners.
|
|
||||||
- `btnStart` — filled button labeled "Start Timer" with a play icon. Uses `Widget.Material3.Button` with 20dp corners.
|
|
||||||
|
|
||||||
**CountdownDialog (timer screen):**
|
|
||||||
- `btnStop` — plain Android `Button` labeled "Stop" (default styling, small)
|
|
||||||
- `btnRestart` — plain Android `Button` labeled "Restart" (default styling, small)
|
|
||||||
- `btnDismiss` — plain Android `Button` labeled "Dismiss" (currently gone, shows after timer completes)
|
|
||||||
|
|
||||||
## Design Requirements
|
|
||||||
|
|
||||||
### 1. Square Aspect Ratio
|
|
||||||
- Change all buttons from `wrap_content` rectangles to a fixed square size (e.g., `120dp` x `120dp` or `100dp` x `100dp`)
|
|
||||||
- Use `app:layout_constraintDimensionRatio="1:1"` where dynamic sizing is needed
|
|
||||||
- Buttons should feel substantial and tappable, not compact
|
|
||||||
|
|
||||||
### 2. Visual Interest — Each button should have its own personality
|
|
||||||
Create custom drawable backgrounds for each button type. Design ideas (pick the best approach):
|
|
||||||
|
|
||||||
- **Circular/spherical buttons** — Buttons that look like physical 3D balls or pucks. Use gradient fills with highlights/shadows to create a convex "pressable" look.
|
|
||||||
- **Neon/glow effect** — Since the app is a bong hit timer, a subtle neon glow that pulses or intensifies during countdown would be thematically appropriate.
|
|
||||||
- **Retro/gaming arcade style** — Bold colors, thick borders, pressed/active states that simulate physical button depression.
|
|
||||||
- **Gradient + icon centering** — For the timer screen, center the stop/restart icons prominently in the square buttons with bold gradient backgrounds.
|
|
||||||
|
|
||||||
### 3. Interactive States
|
|
||||||
Every button must have rich feedback:
|
|
||||||
- **Pressed/active state** — noticeable visual change (shrink, darker shade, or inner glow)
|
|
||||||
- **Default state** — visually engaging with gradients, shadows, or subtle animations
|
|
||||||
- **Disabled state** — reduced opacity but still recognizable
|
|
||||||
|
|
||||||
### 4. Icon Integration
|
|
||||||
Square buttons are perfect for icon-first design:
|
|
||||||
- **Stop button** — large centered pause/stop icon
|
|
||||||
- **Restart button** — large centered refresh/replay icon
|
|
||||||
- **Start button** — center the play icon, consider removing the text label since the icon is universal
|
|
||||||
- **Duration label** — consider a pill-shaped or circular chip that pulses when tapped
|
|
||||||
|
|
||||||
### 5. Color Palette
|
|
||||||
Stay within the existing green theme (`#2E7D32` primary, `#4C6842` secondary, `#E8F5E9`/`#C8E6C9` gradient) but push it further:
|
|
||||||
- Use the primary green for the start/positive actions
|
|
||||||
- A red/coral accent for stop
|
|
||||||
- A neutral or blue for restart/duration selection
|
|
||||||
- Consider gradient transitions within each button (e.g., green to darker green top-to-bottom for a 3D effect)
|
|
||||||
|
|
||||||
### 6. Shadow & Elevation
|
|
||||||
- Add drop shadows to all buttons for depth (use `app:strokeWidth="0dp"` and custom `rippleColor` for Material buttons)
|
|
||||||
- Create a `btn_square_default.xml` and `btn_square_pressed.xml` in `res/drawable/` for each button type
|
|
||||||
- The pressed state should visually simulate "pressing in" (reduced shadow, lighter/darker fill)
|
|
||||||
|
|
||||||
## Technical Implementation
|
|
||||||
|
|
||||||
### New drawable files to create (in `res/drawable/`):
|
|
||||||
- `btn_start_square.xml` — gradient + shadow drawable for start button
|
|
||||||
- `btn_start_square_pressed.xml` — pressed state variant
|
|
||||||
- `btn_stop_square.xml` — gradient + shadow drawable for stop button
|
|
||||||
- `btn_stop_square_pressed.xml` — pressed state variant
|
|
||||||
- `btn_restart_square.xml` — gradient + shadow drawable for restart button
|
|
||||||
- `btn_restart_square_pressed.xml` — pressed state variant
|
|
||||||
- `btn_duration_chip.xml` — drawable for duration selector
|
|
||||||
|
|
||||||
### Layout changes needed:
|
|
||||||
- `activity_main.xml`:
|
|
||||||
- `btnDurationLabel`: change to square dimensions, wrap text/icon in center, apply new drawable
|
|
||||||
- `btnStart`: change to square, center play icon, apply new drawable
|
|
||||||
- `dialog_countdown.xml`:
|
|
||||||
- `btnStop`: change from `Button` to `com.google.android.material.button.MaterialButton`, set square dimensions, apply drawable
|
|
||||||
- `btnRestart`: same treatment
|
|
||||||
- `btnDismiss`: same treatment, keep as gone by default
|
|
||||||
- Adjust the ConstraintLayout chain to accommodate square buttons (may need a horizontal chain with proper spacing)
|
|
||||||
|
|
||||||
### Code changes needed:
|
|
||||||
- `CountdownDialog.kt`: Set button backgrounds programmatically if needed, ensure clickable/pressable states work
|
|
||||||
- No changes to button click logic — only visual overhaul
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Use only Material 3 components already in the dependency (`com.google.android.material:material:1.11.0`)
|
|
||||||
- Traditional XML layouts — no Compose
|
|
||||||
- Minimum SDK 24 — no API 30+ features
|
|
||||||
- Keep the app's existing green aesthetic but make it bolder
|
|
||||||
- Maintain accessibility (minimum 48dp touch target, sufficient contrast)
|
|
||||||
Reference in New Issue
Block a user