From 4570c076ecfc4fb4481deafc200cd47a9d6f6d69 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 27 Feb 2026 23:12:18 -0800 Subject: [PATCH] feat(android): add foreground service, Quick Settings tile, proguard rules --- android/app/proguard-rules.pro | 21 ++++ android/app/src/main/AndroidManifest.xml | 18 ++++ .../app/service/QuickTimerTileService.kt | 27 ++++++ .../app/service/TimerForegroundService.kt | 96 +++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/java/com/chronomind/app/service/QuickTimerTileService.kt create mode 100644 android/app/src/main/java/com/chronomind/app/service/TimerForegroundService.kt diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..0bce152 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# ChronoMind ProGuard Rules + +# Keep Hilt generated classes +-keep class dagger.hilt.** { *; } +-keep class javax.inject.** { *; } +-keep class * extends dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper { *; } + +# Keep Kotlin serialization +-keepattributes *Annotation*, InnerClasses +-dontnote kotlinx.serialization.AnnotationsKt +-keepclassmembers class kotlinx.serialization.json.** { *** Companion; } +-keepclasseswithmembers class kotlinx.serialization.json.** { kotlinx.serialization.KSerializer serializer(...); } +-keep,includedescriptorclasses class com.chronomind.app.**$$serializer { *; } +-keepclassmembers class com.chronomind.app.** { *** Companion; } +-keepclasseswithmembers class com.chronomind.app.** { kotlinx.serialization.KSerializer serializer(...); } + +# Keep Glance widget receivers +-keep class com.chronomind.app.widget.** { *; } + +# Keep notification receivers +-keep class com.chronomind.app.notifications.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8b4a576..986f0d6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -69,6 +69,24 @@ + + + + + + + + + + + tile.label = "Quick Timer" + tile.subtitle = "Create timer" + tile.updateTile() + } + } +} diff --git a/android/app/src/main/java/com/chronomind/app/service/TimerForegroundService.kt b/android/app/src/main/java/com/chronomind/app/service/TimerForegroundService.kt new file mode 100644 index 0000000..084c6e9 --- /dev/null +++ b/android/app/src/main/java/com/chronomind/app/service/TimerForegroundService.kt @@ -0,0 +1,96 @@ +package com.chronomind.app.service + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import androidx.core.app.NotificationCompat +import com.chronomind.app.MainActivity + +class TimerForegroundService : Service() { + + companion object { + const val CHANNEL_ID = "chronomind_foreground" + const val NOTIFICATION_ID = 9999 + const val EXTRA_TIMER_LABEL = "timer_label" + const val EXTRA_TIMER_REMAINING = "timer_remaining" + const val ACTION_STOP = "com.chronomind.STOP_FOREGROUND" + + fun start(context: Context, label: String, remainingFormatted: String) { + val intent = Intent(context, TimerForegroundService::class.java).apply { + putExtra(EXTRA_TIMER_LABEL, label) + putExtra(EXTRA_TIMER_REMAINING, remainingFormatted) + } + context.startForegroundService(intent) + } + + fun stop(context: Context) { + context.stopService(Intent(context, TimerForegroundService::class.java)) + } + } + + override fun onCreate() { + super.onCreate() + createChannel() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent?.action == ACTION_STOP) { + stopForeground(STOP_FOREGROUND_REMOVE) + stopSelf() + return START_NOT_STICKY + } + + val label = intent?.getStringExtra(EXTRA_TIMER_LABEL) ?: "Timer" + val remaining = intent?.getStringExtra(EXTRA_TIMER_REMAINING) ?: "--:--" + + val notification = buildNotification(label, remaining) + startForeground(NOTIFICATION_ID, notification) + + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder? = null + + private fun createChannel() { + val channel = NotificationChannel( + CHANNEL_ID, + "Active Timer", + NotificationManager.IMPORTANCE_LOW + ).apply { + description = "Shows countdown for the active timer" + setShowBadge(false) + } + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.createNotificationChannel(channel) + } + + private fun buildNotification(label: String, remaining: String): Notification { + val openIntent = PendingIntent.getActivity( + this, 0, + Intent(this, MainActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val stopIntent = PendingIntent.getService( + this, 1, + Intent(this, TimerForegroundService::class.java).apply { action = ACTION_STOP }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + return NotificationCompat.Builder(this, CHANNEL_ID) + .setSmallIcon(android.R.drawable.ic_lock_idle_alarm) + .setContentTitle(label) + .setContentText(remaining) + .setOngoing(true) + .setSilent(true) + .setContentIntent(openIntent) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopIntent) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .build() + } +}