diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4cdc27a..ad5a4e7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
-
diff --git a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt
index 500aa50..067358d 100644
--- a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt
+++ b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt
@@ -1,11 +1,14 @@
package com.example.countdowntimer
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
+import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
+import android.view.animation.AccelerateDecelerateInterpolator
class CountdownCanvasView @JvmOverloads constructor(
context: Context,
@@ -14,12 +17,32 @@ class CountdownCanvasView @JvmOverloads constructor(
) : View(context, attrs, defStyleAttr) {
private var textToDraw: String = ""
+ private var progressFraction: Float = 1.0f
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- color = Color.parseColor("#6750A4") // Using the same primary purple
- textSize = 180f // Roughly equivalent to 72sp depending on density
+ color = Color.parseColor("#6750A4")
+ textSize = 180f
textAlign = Paint.Align.CENTER
isFakeBoldText = false
}
+ private val ringPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.parseColor("#D0BCFF")
+ style = Paint.Style.STROKE
+ strokeWidth = 16f
+ }
+ private val ringBgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.parseColor("#E8DEF8")
+ style = Paint.Style.STROKE
+ strokeWidth = 16f
+ }
+ private val rectF = RectF()
+ private val progressAnimator = ValueAnimator.ofFloat(1.0f, 0.0f).apply {
+ duration = 10000
+ interpolator = AccelerateDecelerateInterpolator()
+ addUpdateListener { animation ->
+ progressFraction = animation.animatedValue as Float
+ invalidate()
+ }
+ }
fun setText(text: String) {
if (textToDraw != text) {
@@ -28,15 +51,42 @@ class CountdownCanvasView @JvmOverloads constructor(
}
}
+ fun setProgress(fraction: Float) {
+ progressFraction = fraction
+ invalidate()
+ }
+
+ fun startProgressAnimation(duration: Long) {
+ progressAnimator.duration = duration
+ progressAnimator.start()
+ }
+
+ fun cancelProgressAnimation() {
+ progressAnimator.cancel()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ val inset = ringPaint.strokeWidth / 2f + 12f
+ rectF.set(inset, inset, w - inset, h - inset)
+ }
+
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- if (textToDraw.isEmpty()) return
+ if (textToDraw.isEmpty() || rectF.width() <= 0f) return
+ val cx = rectF.centerX()
+ val cy = rectF.centerY()
+
+ // Draw background ring
+ canvas.drawArc(rectF, -90f, 360f, false, ringBgPaint)
+ // Draw progress ring (remaining time, starts at 12 o'clock)
+ canvas.drawArc(rectF, -90f, 360f * progressFraction, false, ringPaint)
+
+ // Draw text
val fontMetrics = paint.fontMetrics
- val x = width / 2f
- val y = (height / 2f) - ((fontMetrics.descent + fontMetrics.ascent) / 2f)
-
- canvas.drawText(textToDraw, x, y, paint)
+ val textY = (cy - (fontMetrics.descent + fontMetrics.ascent) / 2f)
+ canvas.drawText(textToDraw, cx, textY, paint)
}
}
diff --git a/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt b/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt
index cc85e77..47bc7ef 100644
--- a/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt
+++ b/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt
@@ -2,21 +2,29 @@ package com.example.countdowntimer
import android.app.Dialog
import android.content.Context
+import android.content.SharedPreferences
import android.media.AudioManager
import android.media.ToneGenerator
import android.os.Bundle
import android.os.CountDownTimer
+import android.os.Vibrator
import android.view.View
import android.view.WindowManager
import android.widget.Button
+import com.google.android.material.chip.ChipGroup
-class CountdownDialog(private val context: Context, private val durationMillis: Long) : Dialog(context) {
+class CountdownDialog(
+ private val context: Context,
+ private val durationMillis: Long,
+ private val prefs: SharedPreferences
+) : Dialog(context) {
private lateinit var cvCountdown: CountdownCanvasView
private lateinit var btnStop: Button
private lateinit var btnRestart: Button
private lateinit var btnDismiss: Button
private var timer: CountDownTimer? = null
private var toneGenerator: ToneGenerator? = null
+ private var vibrator: Vibrator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -38,6 +46,7 @@ class CountdownDialog(private val context: Context, private val durationMillis:
btnRestart = findViewById(R.id.btnRestart)
btnDismiss = findViewById(R.id.btnDismiss)
toneGenerator = ToneGenerator(AudioManager.STREAM_ALARM, 100)
+ vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
btnStop.setOnClickListener { cancelTimer() }
btnRestart.setOnClickListener { restartTimer() }
@@ -47,22 +56,32 @@ class CountdownDialog(private val context: Context, private val durationMillis:
}
private fun startTimer() {
+ cvCountdown.setProgress(1f)
timer = object : CountDownTimer(durationMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
val secondsLeft = (millisUntilFinished / 1000).toInt() + 1
- if (secondsLeft < 10) {
+ val progress = millisUntilFinished.toFloat() / durationMillis
+ if (secondsLeft < 10) {
cvCountdown.setText(secondsLeft.toString())
} else {
cvCountdown.setText(context.getString(R.string.countdown_text))
}
+ cvCountdown.setProgress(progress)
if (secondsLeft in 1..3) {
playTone(ToneGenerator.TONE_PROP_BEEP)
+ if (prefs.getBoolean("vibration", true)) {
+ vibrator?.vibrate(50)
+ }
}
}
override fun onFinish() {
cvCountdown.setText(context.getString(R.string.timer_zero))
+ cvCountdown.setProgress(0f)
playTone(ToneGenerator.TONE_SUP_CONFIRM, 500)
+ if (prefs.getBoolean("vibration", true)) {
+ vibrator?.vibrate(200)
+ }
btnStop.visibility = View.GONE
btnRestart.visibility = View.GONE
btnDismiss.visibility = View.VISIBLE
diff --git a/app/src/main/java/com/example/countdowntimer/MainActivity.kt b/app/src/main/java/com/example/countdowntimer/MainActivity.kt
index f007bfd..fed1022 100644
--- a/app/src/main/java/com/example/countdowntimer/MainActivity.kt
+++ b/app/src/main/java/com/example/countdowntimer/MainActivity.kt
@@ -1,18 +1,78 @@
package com.example.countdowntimer
+import android.content.Context
+import android.content.SharedPreferences
import android.os.Bundle
+import android.view.View
+import android.view.inputmethod.InputMethodManager
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
+import com.google.android.material.chip.ChipGroup
class MainActivity : AppCompatActivity() {
- private val TIMER_DURATION_MS = 10000L
+
+ companion object {
+ private const val PREFS_NAME = "timer_prefs"
+ private const val KEY_SELECTED_DURATION = "selected_duration"
+ private const val DEFAULT_DURATION = 10000L
+ }
+
+ private lateinit var prefs: SharedPreferences
+ private lateinit var btnDurationLabel: Button
+ private lateinit var hsvDurationOptions: View
+ private lateinit var chipGroup: ChipGroup
+ private var selectedDuration: Long = DEFAULT_DURATION
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ selectedDuration = prefs.getLong(KEY_SELECTED_DURATION, DEFAULT_DURATION)
+
+ btnDurationLabel = findViewById(R.id.btnDurationLabel)
+ hsvDurationOptions = findViewById(R.id.hsvDurationOptions)
+ chipGroup = findViewById(R.id.chipGroup)
+
+ // Restore selected chip
+ val chipId = when (selectedDuration) {
+ 5000L -> R.id.chip5s
+ 15000L -> R.id.chip15s
+ 30000L -> R.id.chip30s
+ else -> R.id.chip10s
+ }
+ chipGroup.check(chipId)
+
+ btnDurationLabel.setOnClickListener {
+ if (hsvDurationOptions.visibility == View.GONE) {
+ hsvDurationOptions.visibility = View.VISIBLE
+ } else {
+ hsvDurationOptions.visibility = View.GONE
+ (currentFocus ?: btnDurationLabel).let { focused ->
+ focused.clearFocus()
+ val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(hsvDurationOptions.windowToken, 0)
+ }
+ }
+ }
+
+ chipGroup.setOnCheckedChangeListener { _, checkedId ->
+ if (checkedId != -1) {
+ selectedDuration = when (checkedId) {
+ R.id.chip5s -> 5000L
+ R.id.chip10s -> 10000L
+ R.id.chip15s -> 15000L
+ R.id.chip30s -> 30000L
+ else -> DEFAULT_DURATION
+ }
+ prefs.edit().putLong(KEY_SELECTED_DURATION, selectedDuration).apply()
+ hsvDurationOptions.visibility = View.GONE
+ chipGroup.clearCheck()
+ }
+ }
+
findViewById