From f87eb6873011e417b2f0fba999f71c869f85bcc0 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Thu, 15 Jan 2026 11:22:01 +0100 Subject: [PATCH 01/15] Refactor screen usage calculation logic Reworks the logic for calculating app foreground time using UsageEvents, improving accuracy and compatibility with newer Android versions. Introduces separate event queries for lookback and main intervals, and simplifies event handling for ACTIVITY_RESUMED and ACTIVITY_PAUSED. --- .../dev/pranav/reef/util/ScreenUsageHelper.kt | 164 ++++++------------ 1 file changed, 51 insertions(+), 113 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt b/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt index 88bfd6c..615a2f4 100644 --- a/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt +++ b/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt @@ -1,12 +1,14 @@ package dev.pranav.reef.util import android.app.usage.UsageEvents +import android.app.usage.UsageEventsQuery import android.app.usage.UsageStatsManager import android.content.Context import android.os.Build import android.util.Log import java.util.Calendar + object ScreenUsageHelper { private const val TAG = "ScreenUsageHelper" @@ -27,8 +29,6 @@ object ScreenUsageHelper { end: Long, targetPackage: String? = null ): Map { - val usageMap = mutableMapOf() - try { val eventBasedUsage = calculateUsageFromEvents(usageStatsManager, start, end, targetPackage) @@ -51,116 +51,75 @@ object ScreenUsageHelper { end: Long, targetPackage: String? = null ): Map { - val usageMap = mutableMapOf() - val packageForegroundTime = mutableMapOf() - var currentForegroundPackage: String? = null - var foregroundStartTime: Long = 0 + val packageForegroundTimes = mutableMapOf() + val packageStartTimes = mutableMapOf() - val lookbackStart = start - (24 * 60 * 60 * 1000) + var usageEvents: UsageEvents + val event = UsageEvents.Event() runCatching { - val usageEvents = usageStatsManager.queryEvents(lookbackStart, end) - - while (usageEvents.hasNextEvent()) { - val event = UsageEvents.Event() - usageEvents.getNextEvent(event) - - if (event.timeStamp < start) { - if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED || - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && - event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) - ) { - currentForegroundPackage = event.packageName - foregroundStartTime = start - } - continue + val lookBackStart = start - (2 * 60 * 60 * 1000) + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + usageEvents = usageStatsManager.queryEvents(lookBackStart, start) + } else { + val query = UsageEventsQuery.Builder( + lookBackStart, + start, + ).setEventTypes(*intArrayOf(UsageEvents.Event.ACTIVITY_RESUMED)) + + if (targetPackage != null) { + query.setPackageNames(targetPackage) } - if (targetPackage != null && event.packageName != targetPackage) { - if (currentForegroundPackage == targetPackage && - (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED || - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && - event.eventType == UsageEvents.Event.ACTIVITY_RESUMED)) - ) { - val duration = event.timeStamp - foregroundStartTime - if (duration > 0) { - packageForegroundTime[targetPackage] = - packageForegroundTime.getOrDefault(targetPackage, 0L) + duration - } - currentForegroundPackage = null - } - continue + usageEvents = usageStatsManager.queryEvents(query.build())!! + } + + while (usageEvents.hasNextEvent() && usageEvents.getNextEvent(event)) { + if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) { + packageStartTimes[event.packageName] = event.timeStamp } + } + } + + runCatching { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + usageEvents = usageStatsManager.queryEvents(start, end) + } else { + val query = UsageEventsQuery.Builder( + start, + end, + ).setEventTypes(*intArrayOf(UsageEvents.Event.ACTIVITY_RESUMED, UsageEvents.Event.ACTIVITY_PAUSED)) + + if (targetPackage != null) { + query.setPackageNames(targetPackage) + } + + usageEvents = usageStatsManager.queryEvents(query.build())!! + } + while (usageEvents.hasNextEvent() && usageEvents.getNextEvent(event)) { val packageName = event.packageName val timestamp = event.timeStamp when (event.eventType) { UsageEvents.Event.ACTIVITY_RESUMED -> { - if (currentForegroundPackage != null && currentForegroundPackage != packageName) { - val duration = timestamp - foregroundStartTime - if (duration > 0 && foregroundStartTime >= start) { - packageForegroundTime[currentForegroundPackage!!] = - packageForegroundTime.getOrDefault( - currentForegroundPackage!!, - 0L - ) + duration - } - } - currentForegroundPackage = packageName - foregroundStartTime = timestamp + packageStartTimes[packageName] = timestamp } - UsageEvents.Event.ACTIVITY_PAUSED, UsageEvents.Event.ACTIVITY_STOPPED -> { - if (currentForegroundPackage == packageName) { - val duration = timestamp - foregroundStartTime - if (duration > 0 && foregroundStartTime >= start) { - packageForegroundTime[packageName] = - packageForegroundTime.getOrDefault(packageName, 0L) + duration - } - currentForegroundPackage = null + UsageEvents.Event.ACTIVITY_PAUSED -> { + packageStartTimes[packageName]?.let { startTime -> + val duration = timestamp - startTime + packageForegroundTimes[packageName] = + packageForegroundTimes.getOrDefault(packageName, 0L) + duration + packageStartTimes.remove(packageName) } } - - UsageEvents.Event.SCREEN_INTERACTIVE -> { - if (currentForegroundPackage != null) { - foregroundStartTime = timestamp - } - } - - UsageEvents.Event.SCREEN_NON_INTERACTIVE -> { - if (currentForegroundPackage != null) { - val duration = timestamp - foregroundStartTime - if (duration > 0 && foregroundStartTime >= start) { - packageForegroundTime[currentForegroundPackage!!] = - packageForegroundTime.getOrDefault( - currentForegroundPackage!!, - 0L - ) + duration - } - currentForegroundPackage = null - } - } - } - } - - if (currentForegroundPackage != null && foregroundStartTime >= start) { - val duration = end - foregroundStartTime - if (duration > 0) { - packageForegroundTime[currentForegroundPackage!!] = - packageForegroundTime.getOrDefault( - currentForegroundPackage!!, - 0L - ) + duration } } } - packageForegroundTime.forEach { (pkg, time) -> - usageMap[pkg] = time - } - - return usageMap.filterValues { it > 0L } + return packageForegroundTimes.filterValues { it > 0L } } private fun calculateUsageFromStats( @@ -207,25 +166,4 @@ object ScreenUsageHelper { val end = System.currentTimeMillis() return fetchUsageInMs(usageStatsManager, start, end).mapValues { it.value / 1000 } } - - fun getDailyUsage(usageStatsManager: UsageStatsManager, packageName: String): Long { - val cal = Calendar.getInstance() - cal.set(Calendar.HOUR_OF_DAY, 0) - cal.set(Calendar.MINUTE, 0) - cal.set(Calendar.SECOND, 0) - cal.set(Calendar.MILLISECOND, 0) - val startOfDay = cal.timeInMillis - val now = System.currentTimeMillis() - - return fetchUsageInMs(usageStatsManager, startOfDay, now, packageName)[packageName] ?: 0L - } - - fun getTodayUsageMap(usageStatsManager: UsageStatsManager): Map { - val cal = Calendar.getInstance() - cal.set(Calendar.HOUR_OF_DAY, 0) - cal.set(Calendar.MINUTE, 0) - cal.set(Calendar.SECOND, 0) - cal.set(Calendar.MILLISECOND, 0) - return fetchUsageInMs(usageStatsManager, cal.timeInMillis, System.currentTimeMillis()) - } } From 8a449bcb9104f4f32bee7e0d18946ebaa3ca25cd Mon Sep 17 00:00:00 2001 From: alv-cor Date: Fri, 16 Jan 2026 15:31:34 +0100 Subject: [PATCH 02/15] Add Spanish translations for app strings Introduces a new strings.xml file with Spanish translations for all user-facing text in the application, supporting localization for Spanish-speaking users. --- Reef/src/main/res/values-es/strings.xml | 299 ++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 Reef/src/main/res/values-es/strings.xml diff --git a/Reef/src/main/res/values-es/strings.xml b/Reef/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..85f2d34 --- /dev/null +++ b/Reef/src/main/res/values-es/strings.xml @@ -0,0 +1,299 @@ + + + Reef + Servicio de Reef + Reef es una aplicación de código abierto de productividad diseñada para ayudarte a reducir el tiempo de pantalla y mejorar tu productividad. + Servicio de accesibilidad + Reef usa el servicio de accesibilidad para bloquear distracciones y ayudarte a mantener la concentración. + Reef usa el servicio de accesiblidad para forzar los límites de tiempo de pantalla por aplicación. + Estoy de acuerdo + Tiempo restante: %1$s + Modo concentración + Uso de aplicaciones + Monitoriza el tiempo de pantalla + Acceso al uso + Reef usa el acceso al uso para monitorizar el uso de aplicaciones. No se recopila ni se comparte ningún tipo de información. + Estadísticas de uso de aplicaciones + Reef usa el permiso de acceso al uso para ayudarte a controlar el tiempo en pantalla. Ningún dato será envíado desde tu teléfono. + + Permiso de notificaciones + Por favor, concede el permiso de notificaciones para continuar. + Excepción de optimización de batería + Por favor, concede las excepciones de optimización de batería para continuar. + Programar alarmas exactas + Permite a Reef programar alarmas exactas para las rutinas. Esto asegura que el bloqueo de apps empiece y termine a la hora exacta que configures. + + Apps permitidas + Selecciona apps para excluir del modo concentración + + Establecer límite + Eliminar límite + Límite de uso diario + Icono de aplicación + Promedio diario + Pico de uso + Semanal + + Rutinas + Programar sesiones de concentración + + Sin rutinas + Icono de rutina + Activo + Nombre de rutina + Horario + Diario + Semanal + Manual + Hora de inicio: + Hora de fin: + Selecciona los días: + Lunes + Martes + Miércoles + Jueves + Viernes + Sábado + Domingo + Límites de aplicaciones + Añadir app + No hay límites añadidos. Toca \'Añadir app\' para limitar apps específicas + Guardar rutina + Eliminar rutina + Automatiza tu bienestar digital + Crea rutinas inteligentes para gestionar límites automáticamente en días y horas específicos + Aún no hay rutinas + Crea tu primera rutina para automatizar límites de uso de aplicaciones según tu horario + Crear rutina + Acerca de + Atrás + Siguiente + Empezar + Gestiona tu enfoque + Ajustes + + Unirse a la comunidad + Únete a nuestro Discord para conectar con otros usuarios, dar feedback y estar al día de las novedades. + Unirse a Discord + Quizás luego + Apoyar el desarrollo + Si te gusta Reef, considera apoyar su desarrollo. Tus contribuciones ayudan a mantener la app gratuita y de código abierto. + Cualquier aporte ayuda a mantener vivo y mejorando este proyecto. + + Temporizador + Activar modo \'No Molestar\' al concentrarse + Silencia notificaciones durante el modo concentración + Pomodoro + Configurar sesiones y descansos + Bloqueo de aplicaciones + Configurar apps bloqueadas + Info. de la app y créditos + Ajustes Pomodoro + Ajustes Pomodoro + Duraciones + Duraciones + Duración de la sesión de concentración + Descanso corto + Duración del descanso corto + Descanso largo + Duración del descanso largo + Ciclos antes del descanso largo + min. + ciclos + Notificaciones + Sonido + Sonar al cambiar de fase + Elegir sonido + Seleccionar tono de notificación + Vibración + Vibrar al cambiar de fase + Elegir sonido de transición + Automatización + Iniciar descansos de manera automática + Iniciar descanso al terminar la sesión de concentración + Iniciar automáticamente la sesión de concentración + Iniciar sesión de concentración al terminar el descanso + Sonido de transición + Reproducir sonido al cambiar de fase + Vibración al transicionar + Vibrar al cambiar de fase + Seleccionar sonido + Elige un sonido de notificación + + Notificaciones + Configurar alertas y recordatorios + Ajustes de notificación + Recordatorios de concentración + Recibir aviso para iniciar sesión de concentración + Alertas de descanso + Avisar cuando toque descansar + Resumen diario + Recibir informe de tiempo en pantalla a las 21:00 + Tiempo de pantalla diario + Hoy usaste tu teléfono %1$s + Avisos de límite + Avisar al acercarse al límite de uso una app + General + + Editar rutina + ¿Seguro que quieres eliminar \'%1$s\'? No se puede deshacer. + Eliminar + Cancelar + Restablecer + Quitar límite de app + OK + Seleccionar app + Buscar aplicación + No se encontraron apps + Ninguna app coincide con tu búsqueda + Poner límite a %1$s + + + %d minuto + %d minutos + + + + %1$d hora + %1$d horas + + + Introduce un nombre para la rutina + + Activar rutina + ¿Quieres activar \'%1$s\' ahora? + No se aplicaron límites de apps + + + %1$d límite aplicado + %1$d límites aplicados + + + ¡Rutina \'%1$s\' activada! %2$s + Activar + Editar + Sin límites de apps + + Horario desconocido + \ de %1$s a %2$s + a las %1$s + Todos los días + Entre semana + Fines de semana + Activación manual + m + %1$d m + %1$d h + %1$d h %2$d m + < 1 m + 0 + + Modo concentración + Temporizador + Pomodoro + Aumentar horas + Aumentar minutos + Reducir horas + Reducir minutos + Horas + Minutos + Modo Estricto + Modo Flexible + No se permite pausar + Pausa y reanuda cuando quieras + Iniciar + Foco + Descanso corto + Descanso largo + Ciclos + Iniciar Pomodoro + Reducir + Aumentar + Ciclo %1$d/%2$d + Pausado + Tómate un descanso, las aplicaciones han sido desbloqueadas + Sin interrupciones + Reanudar + Pausar + + Límite de uso diario + Historial de uso + %1$d min/día de media + + Versión %1$s + App de productividad de código abierto para reducir el tiempo en pantalla y mejorar tu bienestar digital + Desarrollador + Desarrollador Open Source enfocado en herramientas de productividad y seguridad. + Ver perfil de GitHub + Ayuda a mantener y crecer este proyecto + Donar + Mantén Reef gratis y sin anuncios + Apoyar desarrollo + Reef es totalmente gratis, sin anuncios, rastreo ni costes ocultos. Si te resulta útil, considera una pequeña donación. Tu apoyo lo es todo y mantiene vivo el proyecto. ¡Gracias! + Quizás luego + Donar + Enlaces + Comunidad de Discord + Código fuente + Reportar problema + Software Libre y de Código Abierto + + 1 app rastreada + %1$d apps rastreadas + Ordenar + Por tiempo + Por nombre (A–Z) + Cargando datos de uso… + Ver las %1$d apps + Hoy + Últimos 7 días + 7 Días + Semana anterior + Semana siguiente + %1$d%% + + Apps permitidas + No puedes usar Reef sin activar el servicio de accesibilidad + Permisos necesarios + Para que Reef funcione correctamente, concede los siguientes permisos. + Concedido + Conceder + + Parar + + Pausar + Reanudar + Sesión de foco completada + ¡Bien hecho! Has completado tu sesión de concentración. + Toca Reanudar para empezar + + App bloqueada + %1$s está bloqueada por tu rutina activa + %1$s ha alcanzado su límite + Distracción bloqueada + Estabas usando %1$s + + Bloqueador de contenido + Muestra recordatorios de tiempo y apps bloqueadas. + Sin datos de uso + Rutina activada + Rutina desactivada + %1$s ha terminado + Recordatorio de límite + + %1$s se bloqueará en %2$d minuto + %1$s se bloqueará en %2$d minutos + + + Alertas de rutina + Notificaciones sobre activación y fin de rutinas. + + Recordatorios sobre el uso de pantalla. + Recordatorios de tiempo en pantalla + + Modo Concentración + Notificaciones relacionadas con sesiones de foco. + + ¡Se acabó el descanso! + Hora de volver al trabajo. Tu sesión de foco empieza ahora. + \ No newline at end of file From f3e506d8de5d19301042644ec32eb618a64543c5 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Fri, 16 Jan 2026 17:08:28 +0100 Subject: [PATCH 03/15] Update build configs and dependencies for Reef and appintro Added kotlin-android plugin and set JVM target in Reef and appintro build scripts. Updated gradle.properties with new Android build options. Refreshed library versions in libs.versions.toml, including AGP, Compose BOM, Material3, and others. --- Reef/build.gradle.kts | 3 ++ Reef/src/main/AndroidManifest.xml | 54 ++++++------------------------- appintro/build.gradle.kts | 7 ++++ gradle.properties | 10 ++++++ gradle/libs.versions.toml | 12 +++---- 5 files changed, 36 insertions(+), 50 deletions(-) diff --git a/Reef/build.gradle.kts b/Reef/build.gradle.kts index 5dcb541..3f72bb2 100644 --- a/Reef/build.gradle.kts +++ b/Reef/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.android) } android { @@ -38,6 +39,7 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + kotlin.compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } @@ -51,6 +53,7 @@ android { buildToolsVersion = "36.1.0" ndkVersion = "29.0.14033849 rc4" compileSdkMinor = 1 + } dependencies { diff --git a/Reef/src/main/AndroidManifest.xml b/Reef/src/main/AndroidManifest.xml index 6d48538..1188edc 100644 --- a/Reef/src/main/AndroidManifest.xml +++ b/Reef/src/main/AndroidManifest.xml @@ -94,49 +94,15 @@ - - - + + + + + + + + + + - - diff --git a/appintro/build.gradle.kts b/appintro/build.gradle.kts index 746d0f8..add799f 100644 --- a/appintro/build.gradle.kts +++ b/appintro/build.gradle.kts @@ -1,6 +1,9 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.android) } android { @@ -35,6 +38,10 @@ android { buildFeatures { compose = true } + + kotlin.compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } } dependencies { diff --git a/gradle.properties b/gradle.properties index e297c4d..4d20ad9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,3 +25,13 @@ org.gradle.workers.max=4 org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true +android.defaults.buildfeatures.resvalues=true +android.sdk.defaultTargetSdkToCompileSdkIfUnset=false +android.enableAppCompileTimeRClass=false +android.usesSdkInManifest.disallowed=false +android.uniquePackageNames=false +android.dependency.useConstraints=true +android.r8.strictFullModeForKeepRules=false +android.r8.optimizedResourceShrinking=false +android.builtInKotlin=false +android.newDsl=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc7ee2c..b37e590 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "9.1.0-alpha03" +agp = "9.0.0" coreSplashscreen = "1.2.0" kotlin = "2.3.0" coreKtx = "1.17.0" @@ -8,10 +8,10 @@ material = "1.14.0-alpha08" activity = "1.12.2" googleServices = "4.4.4" gson = "2.13.2" -composeBom = "2025.12.01" -material3 = "1.5.0-alpha11" -uiTextGoogleFonts = "1.10.0" -vico = "2.3.6" +composeBom = "2026.01.00" +material3 = "1.5.0-alpha12" +uiTextGoogleFonts = "1.10.1" +vico = "2.4.1" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" @@ -50,7 +50,7 @@ androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.3.0" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } android-library = { id = "com.android.library", version.ref = "agp" } From ea3a2542b8bf73139188f4b52447c590681b8b04 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Fri, 16 Jan 2026 18:15:39 +0100 Subject: [PATCH 04/15] Update AndroidManifest.xml Remove `android:process=":blocker_process` since it makes the blockservice fail --- Reef/src/main/AndroidManifest.xml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Reef/src/main/AndroidManifest.xml b/Reef/src/main/AndroidManifest.xml index 1188edc..f9702ba 100644 --- a/Reef/src/main/AndroidManifest.xml +++ b/Reef/src/main/AndroidManifest.xml @@ -62,7 +62,6 @@ android:exported="true" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:foregroundServiceType="specialUse" - android:process=":blocker_process" tools:ignore="AccessibilityPolicy"> @@ -72,7 +71,6 @@ android:resource="@xml/blocker_configuration" /> - - - - - - - - - - - - From 1c94d3e16d03a103da9870fab57fb8f5751e556d Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:11:07 +0100 Subject: [PATCH 05/15] Add InitializationProvider to AndroidManifest Added androidx.startup.InitializationProvider to the manifest to support initialization of WorkManager. The provider is set as non-exported and includes meta-data for WorkManagerInitializer, with node removal specified for merging. --- Reef/src/main/AndroidManifest.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Reef/src/main/AndroidManifest.xml b/Reef/src/main/AndroidManifest.xml index f9702ba..12e6efd 100644 --- a/Reef/src/main/AndroidManifest.xml +++ b/Reef/src/main/AndroidManifest.xml @@ -91,5 +91,16 @@ + + + + From eae942ba46f8ad9a483167ebc55142c24a86eba6 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:11:16 +0100 Subject: [PATCH 06/15] Replace hardcoded strings with resources in UI Updated MainActivity to use string resources for navigation bar labels and time suffixes instead of hardcoded strings. This improves localization and consistency across the app. --- Reef/src/main/java/dev/pranav/reef/MainActivity.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/MainActivity.kt b/Reef/src/main/java/dev/pranav/reef/MainActivity.kt index 5ff5f05..fdc8fcd 100644 --- a/Reef/src/main/java/dev/pranav/reef/MainActivity.kt +++ b/Reef/src/main/java/dev/pranav/reef/MainActivity.kt @@ -264,17 +264,17 @@ class MainActivity: ComponentActivity() { R.string.hour_min_short_suffix, hours, minutes - ) + " today" + ) + " " + getString(R.string.today) hours > 0 -> getString( R.string.hours_short_format, hours - ) + " today" + ) + " " + getString(R.string.today) minutes > 0 -> getString( R.string.minutes_short_format, minutes - ) + " today" + ) + " " + getString(R.string.today) else -> getString(R.string.less_than_one_minute) } @@ -761,25 +761,25 @@ private fun ReefBottomNavBar( ) { BottomNavItem( icon = Icons.Outlined.Home, - label = "Home", + label = stringResource(R.string.nav_home), selected = selectedItem == 0, onClick = { onItemSelected(0) } ) BottomNavItem( icon = Icons.Rounded.BarChart, - label = "Stats", + label = stringResource(R.string.nav_stats), selected = selectedItem == 1, onClick = { onItemSelected(1) } ) BottomNavItem( icon = Icons.Rounded.SelfImprovement, - label = "Focus", + label = stringResource(R.string.nav_focus), selected = selectedItem == 2, onClick = { onItemSelected(2) } ) BottomNavItem( icon = Icons.Outlined.Settings, - label = "Settings", + label = stringResource(R.string.nav_settings), selected = selectedItem == 3, onClick = { onItemSelected(3) } ) From d38c92780fa89e31b98f5e763af30d6d8b01de94 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:11:21 +0100 Subject: [PATCH 07/15] Update MainScreen.kt --- .../main/java/dev/pranav/reef/MainScreen.kt | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/MainScreen.kt b/Reef/src/main/java/dev/pranav/reef/MainScreen.kt index 921399f..7ca425b 100644 --- a/Reef/src/main/java/dev/pranav/reef/MainScreen.kt +++ b/Reef/src/main/java/dev/pranav/reef/MainScreen.kt @@ -1,5 +1,6 @@ package dev.pranav.reef +import android.content.Context import android.content.Intent import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState @@ -105,7 +106,8 @@ fun HomeContent( TimeLimitsCard( modifier = Modifier.weight(1f), onClick = onNavigateToWhitelist, - whitelistedCount = whitelistedAppsCount + whitelistedCount = whitelistedAppsCount, + context = context ) } @@ -193,7 +195,7 @@ private fun FocusModeCard( Spacer(Modifier.height(24.dp)) Text( - text = "Focus Mode", + text = stringResource(R.string.focus_mode), style = MaterialTheme.typography.headlineLarge.copy( fontWeight = FontWeight.Bold ), @@ -204,7 +206,7 @@ private fun FocusModeCard( Spacer(Modifier.height(8.dp)) Text( - text = "Slide to start a deep work\nsession", + text = stringResource(R.string.slide_description), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f), lineHeight = 22.sp, @@ -281,7 +283,7 @@ private fun FocusTogglePill( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Release", + text = stringResource(R.string.release), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onPrimaryContainer.copy( alpha = (1f - progress).coerceIn(0.3f, 0.7f) @@ -296,7 +298,7 @@ private fun FocusTogglePill( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Slide", + text = stringResource(R.string.slide), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onPrimaryContainer.copy( alpha = (1f - progress).coerceIn(0.3f, 0.7f) @@ -368,7 +370,7 @@ private fun AppUsageCard( Column { Text( - text = "App Usage", + text = stringResource(R.string.app_usage), style = MaterialTheme.typography.titleLarge.copy( fontWeight = FontWeight.Bold ), @@ -389,7 +391,8 @@ private fun AppUsageCard( private fun TimeLimitsCard( modifier: Modifier = Modifier, onClick: () -> Unit, - whitelistedCount: Int = 0 + whitelistedCount: Int = 0, + context: Context, ) { Card( onClick = onClick, @@ -423,7 +426,7 @@ private fun TimeLimitsCard( Column { Text( - text = "Whitelist", + text = stringResource(R.string.whitelist_apps), style = MaterialTheme.typography.titleLarge.copy( fontWeight = FontWeight.Bold ), @@ -431,7 +434,11 @@ private fun TimeLimitsCard( color = MaterialTheme.colorScheme.onTertiaryContainer ) Text( - text = "$whitelistedCount apps allowed", + text = context.resources.getQuantityString( + R.plurals.whitelisted_apps, + whitelistedCount, + whitelistedCount + ), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f) ) @@ -475,14 +482,14 @@ private fun RoutinesCard(onClick: () -> Unit) { Column(modifier = Modifier.weight(1f)) { Text( - text = "Routines", + text = stringResource(R.string.routines), style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold ), color = MaterialTheme.colorScheme.onSurface ) Text( - text = "Scheduled: \"Deep Work\" at 2:00 PM", + text = stringResource(R.string.sample_routine), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -565,7 +572,7 @@ private fun PomodoroTimerCard( text = if (isActive) { currentTimerState.lowercase().replaceFirstChar { it.uppercase() } } else { - "Pomodoro Timer" + stringResource(R.string.pomodoro) }, style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold @@ -574,9 +581,13 @@ private fun PomodoroTimerCard( ) Text( text = if (isActive) { - if (isPaused) "Paused • $currentTimeLeft" else "In progress • $currentTimeLeft" + if (isPaused) { + stringResource(R.string.paused_focus_session, currentTimeLeft) + } else { + stringResource(R.string.in_progress_focus_session, currentTimeLeft) + } } else { - "Start a focus session" + stringResource(R.string.start_focus_session) }, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant From 70f13af3bf77ede468b770ce13f0fbcb654459ab Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:11:29 +0100 Subject: [PATCH 08/15] Add 5-minute option to AppSelectorDialog Introduced a 5-minute selection to the time options in AppSelectorDialog for improved granularity when setting durations. --- .../src/main/java/dev/pranav/reef/screens/CreateRoutineScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Reef/src/main/java/dev/pranav/reef/screens/CreateRoutineScreen.kt b/Reef/src/main/java/dev/pranav/reef/screens/CreateRoutineScreen.kt index c9190a5..48fc7a8 100644 --- a/Reef/src/main/java/dev/pranav/reef/screens/CreateRoutineScreen.kt +++ b/Reef/src/main/java/dev/pranav/reef/screens/CreateRoutineScreen.kt @@ -667,6 +667,7 @@ private fun AppSelectorDialog( Column { listOf( pluralStringResource(R.plurals.minutes_label, 0, 0) to 0, + pluralStringResource(R.plurals.minutes_label, 5, 5) to 5, pluralStringResource(R.plurals.minutes_label, 15, 15) to 15, pluralStringResource(R.plurals.minutes_label, 30, 30) to 30, pluralStringResource(R.plurals.hours_label, 1, 1) to 60, From 81b9e9d1f91e7d7bfa9fd63896e297c5a2211366 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:12:00 +0100 Subject: [PATCH 09/15] Fix week offset and day calculation logic in AppUsageViewModel Corrects the calculation of days for week offset and day iteration in AppUsageViewModel. Updates logic to properly add/subtract days and checks for usage data more accurately, ensuring correct navigation and data retrieval for previous weeks. --- .../reef/ui/appusage/AppUsageViewModel.kt | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/ui/appusage/AppUsageViewModel.kt b/Reef/src/main/java/dev/pranav/reef/ui/appusage/AppUsageViewModel.kt index edb0adf..d206178 100644 --- a/Reef/src/main/java/dev/pranav/reef/ui/appusage/AppUsageViewModel.kt +++ b/Reef/src/main/java/dev/pranav/reef/ui/appusage/AppUsageViewModel.kt @@ -275,8 +275,8 @@ class AppUsageViewModel( repeat(7) { i -> val cal = Calendar.getInstance() - val daysAgo = (_weekOffset.intValue * 7) + (6 - i) - cal.add(Calendar.DAY_OF_YEAR, -daysAgo) + val daysAgo = (_weekOffset.intValue - 1) * 7 + i + 1 + cal.add(Calendar.DAY_OF_YEAR, daysAgo) cal.set(Calendar.HOUR_OF_DAY, 0) cal.set(Calendar.MINUTE, 0) @@ -316,8 +316,8 @@ class AppUsageViewModel( private fun checkPreviousWeekData() { viewModelScope.launch(Dispatchers.IO) { val calendar = Calendar.getInstance() - val daysToSubtract = ((_weekOffset.intValue - 1) * 7) + 6 - calendar.add(Calendar.DAY_OF_YEAR, -daysToSubtract) + val daysToSubtract = (_weekOffset.intValue - 1) * 7 + calendar.add(Calendar.DAY_OF_YEAR, daysToSubtract) val maxWeeksBack = 13 if (_weekOffset.intValue <= -maxWeeksBack) { @@ -326,28 +326,29 @@ class AppUsageViewModel( } var hasData = false - repeat(7) { + for (i in 0 until 7) { val start = calendar.clone() as Calendar - start.set(Calendar.HOUR_OF_DAY, 0); start.set(Calendar.MINUTE, 0); start.set( - Calendar.SECOND, - 0 - ); start.set(Calendar.MILLISECOND, 0) + start.set(Calendar.HOUR_OF_DAY, 0) + start.set(Calendar.MINUTE, 0) + start.set(Calendar.SECOND,0) + start.set(Calendar.MILLISECOND, 0) + val end = start.clone() as Calendar - end.set(Calendar.HOUR_OF_DAY, 23); end.set( - Calendar.MINUTE, - 59 - ); end.set(Calendar.SECOND, 59); end.set(Calendar.MILLISECOND, 999) + end.set(Calendar.HOUR_OF_DAY, 23) + end.set(Calendar.MINUTE,59) + end.set(Calendar.SECOND, 59) + end.set(Calendar.MILLISECOND, 999) if (ScreenUsageHelper.calculateUsage( context, usageStatsManager, start.timeInMillis, end.timeInMillis - ).values.sum() > 0 + ).values.any { it > 0} ) { hasData = true - return@repeat + break } - calendar.add(Calendar.DAY_OF_YEAR, 1) + calendar.add(Calendar.DAY_OF_YEAR, -1) } withContext(Dispatchers.Main) { _canGoPrevious.value = hasData } From daf9fd63390b90a6103bb264d179e7102af1d403 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:12:15 +0100 Subject: [PATCH 10/15] Add new focus session and navigation strings Introduces new string resources for focus session states, slide actions, sample routines, and bottom navigation in both English and Spanish. Also updates several existing strings for consistency and clarity. --- Reef/src/main/res/values-es/strings.xml | 30 ++++++++++++++++++++----- Reef/src/main/res/values/strings.xml | 23 ++++++++++++++++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Reef/src/main/res/values-es/strings.xml b/Reef/src/main/res/values-es/strings.xml index 85f2d34..182a6f2 100644 --- a/Reef/src/main/res/values-es/strings.xml +++ b/Reef/src/main/res/values-es/strings.xml @@ -25,6 +25,10 @@ Apps permitidas Selecciona apps para excluir del modo concentración + + %1$s app permitida + %1$s apps permitidas + Establecer límite Eliminar límite @@ -198,11 +202,11 @@ Horas Minutos Modo Estricto - Modo Flexible + Modo flexible No se permite pausar Pausa y reanuda cuando quieras Iniciar - Foco + Concentración Descanso corto Descanso largo Ciclos @@ -263,7 +267,7 @@ Pausar Reanudar - Sesión de foco completada + Sesión de concentración completada ¡Bien hecho! Has completado tu sesión de concentración. Toca Reanudar para empezar @@ -291,9 +295,23 @@ Recordatorios sobre el uso de pantalla. Recordatorios de tiempo en pantalla - Modo Concentración - Notificaciones relacionadas con sesiones de foco. + Modo concentración + Notificaciones relacionadas con sesiones de concentración. ¡Se acabó el descanso! - Hora de volver al trabajo. Tu sesión de foco empieza ahora. + Hora de volver al trabajo. Tu sesión de concentración empieza ahora. + Deslizar + Soltar + Desliza para comenzar una\nsesión de trabajo + Por ejemplo, configura la rutina \"Trabajo\" a las 14:00 + + Comienza una sesión de concentración + En pausa • %1$s + En progreso • %1$s + + + Inicio + Estadísticas + Concentración + Ajustes \ No newline at end of file diff --git a/Reef/src/main/res/values/strings.xml b/Reef/src/main/res/values/strings.xml index 84fe7bb..604ad19 100644 --- a/Reef/src/main/res/values/strings.xml +++ b/Reef/src/main/res/values/strings.xml @@ -23,6 +23,11 @@ Whitelist apps Select apps to exclude from focus mode + + %1$s app allowed + %1$s apps allowed + + Set Limit Remove Limit @@ -308,4 +313,20 @@ Break Over! Time to get back to work. Your focus session is starting now. - + + Slide + Release + Slide to start a deep work\nsession + "For example, schedule \"Deep Work\" routine at 2:00 PM" + + Start a focus session + Paused • %1$s + In progress • %1$s + + + Home + Stats + Focus + Settings + + \ No newline at end of file From c20bb7a7716f27f7cf7d57f20b53ae8b66fd7699 Mon Sep 17 00:00:00 2001 From: alv-cor Date: Sun, 18 Jan 2026 14:12:30 +0100 Subject: [PATCH 11/15] Refactor usage event querying and improve foreground time logic Extracted event querying into a helper function for cleaner code. Improved logic for tracking package foreground times by handling noisy packages and ensuring accurate duration calculation. This refactor enhances maintainability and correctness of screen usage tracking. --- .../dev/pranav/reef/util/ScreenUsageHelper.kt | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt b/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt index 615a2f4..cd154f0 100644 --- a/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt +++ b/Reef/src/main/java/dev/pranav/reef/util/ScreenUsageHelper.kt @@ -60,43 +60,23 @@ object ScreenUsageHelper { runCatching { val lookBackStart = start - (2 * 60 * 60 * 1000) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { - usageEvents = usageStatsManager.queryEvents(lookBackStart, start) - } else { - val query = UsageEventsQuery.Builder( - lookBackStart, - start, - ).setEventTypes(*intArrayOf(UsageEvents.Event.ACTIVITY_RESUMED)) - - if (targetPackage != null) { - query.setPackageNames(targetPackage) - } - - usageEvents = usageStatsManager.queryEvents(query.build())!! - } + usageEvents = queryEvents(usageStatsManager, lookBackStart, start, targetPackage) while (usageEvents.hasNextEvent() && usageEvents.getNextEvent(event)) { - if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) { - packageStartTimes[event.packageName] = event.timeStamp + when (event.eventType) { + UsageEvents.Event.ACTIVITY_RESUMED -> { + packageStartTimes[event.packageName] = start + } + + UsageEvents.Event.ACTIVITY_PAUSED -> { + packageStartTimes.remove(event.packageName) + } } } } runCatching { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { - usageEvents = usageStatsManager.queryEvents(start, end) - } else { - val query = UsageEventsQuery.Builder( - start, - end, - ).setEventTypes(*intArrayOf(UsageEvents.Event.ACTIVITY_RESUMED, UsageEvents.Event.ACTIVITY_PAUSED)) - - if (targetPackage != null) { - query.setPackageNames(targetPackage) - } - - usageEvents = usageStatsManager.queryEvents(query.build())!! - } + usageEvents = queryEvents(usageStatsManager, start, end) while (usageEvents.hasNextEvent() && usageEvents.getNextEvent(event)) { val packageName = event.packageName @@ -105,6 +85,10 @@ object ScreenUsageHelper { when (event.eventType) { UsageEvents.Event.ACTIVITY_RESUMED -> { packageStartTimes[packageName] = timestamp + if (packageStartTimes.count() == 3) { + val noisyPackage = packageStartTimes.minByOrNull { it.value } + packageStartTimes.remove(noisyPackage!!.key) + } } UsageEvents.Event.ACTIVITY_PAUSED -> { @@ -117,6 +101,16 @@ object ScreenUsageHelper { } } } + + if (packageStartTimes.isNotEmpty()) { + val latestPackage = packageStartTimes.maxByOrNull { it.value } + packageStartTimes[latestPackage!!.key]?.let { startTime -> + val duration = end - startTime + packageForegroundTimes[latestPackage.key] = + packageForegroundTimes.getOrDefault(targetPackage, 0L) + duration + } + } + } return packageForegroundTimes.filterValues { it > 0L } @@ -166,4 +160,24 @@ object ScreenUsageHelper { val end = System.currentTimeMillis() return fetchUsageInMs(usageStatsManager, start, end).mapValues { it.value / 1000 } } + + private fun queryEvents(usageStatsManager: UsageStatsManager, start: Long, end: Long, targetPackage: String? = null) : UsageEvents { + var usageEvents: UsageEvents + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + usageEvents = usageStatsManager.queryEvents(start, end) + } else { + val query = UsageEventsQuery.Builder( + start, + end, + ).setEventTypes(*intArrayOf(UsageEvents.Event.ACTIVITY_RESUMED, UsageEvents.Event.ACTIVITY_PAUSED)) + + if (targetPackage != null) { + query.setPackageNames(targetPackage) + } + + usageEvents = usageStatsManager.queryEvents(query.build())!! + } + + return usageEvents + } } From 484ba407049ea7c1dcdff521f0b033b74984e413 Mon Sep 17 00:00:00 2001 From: invoke Date: Sun, 18 Jan 2026 21:12:21 +0600 Subject: [PATCH 12/15] Remove `kotlin-android` plugin --- appintro/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/appintro/build.gradle.kts b/appintro/build.gradle.kts index add799f..b022757 100644 --- a/appintro/build.gradle.kts +++ b/appintro/build.gradle.kts @@ -3,7 +3,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.compose) - alias(libs.plugins.kotlin.android) } android { From bc9f613f1da28ef019cedcc6f317904021926172 Mon Sep 17 00:00:00 2001 From: invoke Date: Sun, 18 Jan 2026 03:13:24 -1200 Subject: [PATCH 13/15] Remove `kotlin-android` plugin --- Reef/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/Reef/build.gradle.kts b/Reef/build.gradle.kts index 3f72bb2..9265616 100644 --- a/Reef/build.gradle.kts +++ b/Reef/build.gradle.kts @@ -4,7 +4,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.kotlin.android) } android { From 5a95a42b175ade44c36cd449d7a88f03ab059e9a Mon Sep 17 00:00:00 2001 From: invoke Date: Sun, 18 Jan 2026 03:15:01 -1200 Subject: [PATCH 14/15] Clean up gradle.properties by removing unused settings Removed several deprecated Android Gradle properties. --- gradle.properties | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4d20ad9..e297c4d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,13 +25,3 @@ org.gradle.workers.max=4 org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true -android.defaults.buildfeatures.resvalues=true -android.sdk.defaultTargetSdkToCompileSdkIfUnset=false -android.enableAppCompileTimeRClass=false -android.usesSdkInManifest.disallowed=false -android.uniquePackageNames=false -android.dependency.useConstraints=true -android.r8.strictFullModeForKeepRules=false -android.r8.optimizedResourceShrinking=false -android.builtInKotlin=false -android.newDsl=false From 01a6a61b4a45fcf9e1dc28174ed3a28fe6c89dcc Mon Sep 17 00:00:00 2001 From: invoke Date: Mon, 19 Jan 2026 02:16:24 +1100 Subject: [PATCH 15/15] Update AGP version to 9.1.0-alpha05 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b37e590..0704527 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "9.0.0" +agp = "9.1.0-alpha05" coreSplashscreen = "1.2.0" kotlin = "2.3.0" coreKtx = "1.17.0"