diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cdc27a..315b814 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - + @@ -16,5 +17,4 @@ - diff --git a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt index 500aa50..19c5c14 100644 --- a/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt +++ b/app/src/main/java/com/example/countdowntimer/CountdownCanvasView.kt @@ -4,6 +4,7 @@ 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 @@ -14,12 +15,24 @@ 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("#6750A4") + style = Paint.Style.STROKE + strokeWidth = 16f + } + private val ringBgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.parseColor("#D0BCFF") + style = Paint.Style.STROKE + strokeWidth = 16f + } + private val rectF = RectF() fun setText(text: String) { if (textToDraw != text) { @@ -28,15 +41,34 @@ class CountdownCanvasView @JvmOverloads constructor( } } + fun setProgress(fraction: Float) { + progressFraction = fraction.coerceIn(0f, 1f) + invalidate() + } + + 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 full background ring (always visible track) + val bgRect = RectF(rectF.left, rectF.top, rectF.right, rectF.bottom) + canvas.drawArc(bgRect, -90f, 360f, false, ringBgPaint) + // Draw progress arc on top (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..fc7391c 100644 --- a/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt +++ b/app/src/main/java/com/example/countdowntimer/CountdownDialog.kt @@ -2,21 +2,28 @@ 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 -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 +45,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 +55,34 @@ 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) { - cvCountdown.setText(secondsLeft.toString()) - } else { + val progress = millisUntilFinished.toFloat() / durationMillis + cvCountdown.setProgress(progress) + + if (millisUntilFinished >= durationMillis) { cvCountdown.setText(context.getString(R.string.countdown_text)) + } else { + val secondsLeft = (millisUntilFinished / 1000).toInt() + 1 + cvCountdown.setText(secondsLeft.toString()) } - if (secondsLeft in 1..3) { + + if (millisUntilFinished <= 3000 && millisUntilFinished > 1000) { 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..0247f30 100644 --- a/app/src/main/java/com/example/countdowntimer/MainActivity.kt +++ b/app/src/main/java/com/example/countdowntimer/MainActivity.kt @@ -1,18 +1,88 @@ 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 + + private fun durationToChipId(duration: Long): Int = when (duration) { + 5000L -> R.id.chip5s + 15000L -> R.id.chip15s + 30000L -> R.id.chip30s + else -> R.id.chip10s + } + + private fun chipIdToDuration(chipId: Int): Long = when (chipId) { + R.id.chip5s -> 5000L + R.id.chip10s -> 10000L + R.id.chip15s -> 15000L + R.id.chip30s -> 30000L + else -> DEFAULT_DURATION + } + + private fun updateDurationLabel() { + btnDurationLabel.text = "${selectedDuration / 1000}s" + } 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) + + updateDurationLabel() + + // Restore selected chip + val chipId = durationToChipId(selectedDuration) + 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 = chipIdToDuration(checkedId) + prefs.edit().putLong(KEY_SELECTED_DURATION, selectedDuration).apply() + updateDurationLabel() + hsvDurationOptions.visibility = View.GONE + } + } + findViewById