From 7a91ecba18060b2b94db6a79d1de9d4d77974e65 Mon Sep 17 00:00:00 2001 From: kfj001 Date: Sat, 30 May 2026 10:51:36 -0700 Subject: [PATCH] visual polish: add pulsing start button and color-shifting progress ring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Countdown ring interpolates green → yellow → red as timer progresses - Start button pulses continuously to draw attention - Pulse stops while dialog is shown and restarts on resume --- .../countdowntimer/CountdownCanvasView.kt | 7 +++ .../example/countdowntimer/CountdownDialog.kt | 19 ++++++++ .../example/countdowntimer/MainActivity.kt | 44 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt index 05d82fd..36d3fb8 100644 --- a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt +++ b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt @@ -47,6 +47,13 @@ class CountdownCanvasView @JvmOverloads constructor( invalidate() } + fun setColor(color: Int) { + if (ringPaint.color != color) { + ringPaint.color = color + invalidate() + } + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) val inset = ringPaint.strokeWidth / 2f + 12f diff --git a/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt b/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt index 9283278..0f84577 100644 --- a/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt +++ b/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt @@ -3,6 +3,8 @@ package com.example.countdowntimer import android.app.Dialog import android.content.Context import android.content.SharedPreferences +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils import android.media.AudioManager import android.media.ToneGenerator import android.os.Bundle @@ -56,11 +58,28 @@ class CountdownDialog( private fun startTimer() { cvCountdown.setProgress(1f) + cvCountdown.setColor(ContextCompat.getColor(context, R.color.md_color_ring_progress)) timer = object : CountDownTimer(durationMillis, 1000) { override fun onTick(millisUntilFinished: Long) { val progress = millisUntilFinished.toFloat() / durationMillis cvCountdown.setProgress(progress) + // Interpolate ring color: green → yellow at 50% → red at end + val green = android.graphics.Color.parseColor("#2E7D32") + val yellow = android.graphics.Color.parseColor("#FFC107") + val red = android.graphics.Color.parseColor("#F44336") + val ringColor = when { + progress >= 0.5f -> { + val t = (progress - 0.5f) * 2f + ColorUtils.blendARGB(green, yellow, t) + } + else -> { + val t = progress * 2f + ColorUtils.blendARGB(yellow, red, t) + } + } + cvCountdown.setColor(ringColor) + val secondsLeft = (millisUntilFinished / 1000).toInt() + 1 cvCountdown.setText(secondsLeft.toString()) diff --git a/app/src/main/java/com/example/countdowntimer/MainActivity.kt b/app/src/main/java/com/example/countdowntimer/MainActivity.kt index 33a476f..ad82ed0 100644 --- a/app/src/main/java/com/example/countdowntimer/MainActivity.kt +++ b/app/src/main/java/com/example/countdowntimer/MainActivity.kt @@ -11,6 +11,8 @@ import android.view.inputmethod.InputMethodManager import android.widget.Button import androidx.appcompat.app.AppCompatActivity import com.google.android.material.chip.ChipGroup +import android.animation.ValueAnimator +import android.view.animation.AnticipateOvershootInterpolator class MainActivity : AppCompatActivity() { @@ -24,9 +26,11 @@ class MainActivity : AppCompatActivity() { private lateinit var btnDurationLabel: Button private lateinit var hsvDurationOptions: View private lateinit var chipGroup: ChipGroup + private lateinit var btnStart: View private lateinit var vibrator: Vibrator private var selectedDuration: Long = DEFAULT_DURATION private var lastSelectedChipId: Int = -1 + private var pulseAnimator: ValueAnimator? = null private fun durationToChipId(duration: Long): Int = when (duration) { 5000L -> R.id.chip5s @@ -57,6 +61,30 @@ class MainActivity : AppCompatActivity() { } } + private fun startPulseAnimation() { + pulseAnimator = ValueAnimator.ofFloat(1f, 0.78f, 1f).apply { + duration = 1500 + repeatMode = ValueAnimator.REVERSE + repeatCount = ValueAnimator.INFINITE + interpolator = AnticipateOvershootInterpolator(1f) + addUpdateListener { animation -> + val fraction = animation.animatedValue as Float + btnStart.scaleX = fraction + btnStart.scaleY = fraction + btnStart.alpha = fraction + } + } + pulseAnimator?.start() + } + + private fun stopPulseAnimation() { + pulseAnimator?.cancel() + pulseAnimator = null + btnStart.scaleX = 1f + btnStart.scaleY = 1f + btnStart.alpha = 1f + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -67,6 +95,7 @@ class MainActivity : AppCompatActivity() { btnDurationLabel = findViewById(R.id.btnDurationLabel) hsvDurationOptions = findViewById(R.id.hsvDurationOptions) chipGroup = findViewById(R.id.chipGroup) + btnStart = findViewById(R.id.btnStart) val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager vibrator = vibratorManager.defaultVibrator @@ -111,7 +140,22 @@ class MainActivity : AppCompatActivity() { findViewById(R.id.chip30s).setOnClickListener { onChipClicked(R.id.chip30s) } findViewById(R.id.btnStart).setOnClickListener { + stopPulseAnimation() CountdownDialog(this, selectedDuration, prefs).show() } + + startPulseAnimation() + } + + override fun onResume() { + super.onResume() + if (!isFinishing && !isDestroyed) { + startPulseAnimation() + } + } + + override fun onPause() { + super.onPause() + stopPulseAnimation() } }