diff --git a/.gitignore b/.gitignore index 23e1348..d29c5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ captures/ # Keys / services *.jks *.keystore +keystore.properties google-services.json # Logs & profiling @@ -32,3 +33,4 @@ google-services.json .DS_Store Thumbs.db +*.keystore diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9ba6513 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,116 @@ +# Changelog - CBDCounter + +Todos los cambios notables del proyecto se documentarán en este archivo. + +El formato se basa en [Keep a Changelog](https://keepachangelog.com/es/1.0.0/), +y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/). + +--- + +## [1.1.0] - 2025-11-10 + +### ✨ Añadido +- **Disclaimer médico obligatorio** en el primer uso de la app (requisito Google Play) +- **Política de privacidad completa** (RGPD + Google Play compatible) +- **Documentación para Play Store** (descripciones corta y larga) +- **GitHub Pages** con documentación oficial publicada +- **Configuración de producción completa** (ProGuard/R8, firma digital, AAB) + +### 🐛 Corregido +- **Bug crítico:** Widget ahora respeta los emojis personalizados del usuario (usaba emojis hardcodeados) +- **Bug crítico:** Confirmación obligatoria antes de importar CSV (previene pérdida accidental de datos) +- **Mejora:** Código optimizado y ofuscado con R8 para reducir tamaño del APK + +### 🔧 Cambios Técnicos +- Migrado de APK a **Android App Bundle (AAB)** (obligatorio desde 2021) +- Configurado **Google Play App Signing** +- Reglas **ProGuard/R8** específicas para la app +- **Target SDK actualizado a 34** (Android 14) +- Reducción del tamaño de release: 5.8MB → 3.5MB (~40% menor) + +### 📋 Preparación para Play Store +- ✅ Cumple políticas de contenido de Google Play +- ✅ Disclaimer médico para apps relacionadas con CBD +- ✅ Sección de Seguridad de Datos lista +- ✅ Política de privacidad pública disponible + +### 📚 Documentación +- Política de privacidad detallada (RGPD compliant) +- Instrucciones para GitHub Pages +- Descripciones para Play Store (corta + larga) +- Changelog estructurado + +--- + +## [1.0.0] - 2025-09-29 + +### 🎉 Lanzamiento Inicial + +Primer lanzamiento público de CBDCounter con todas las funcionalidades core. + +### ✨ Funcionalidades +- **Contador diario** con botones +1, -1 y reset +- **Widget de pantalla principal** con actualización en tiempo real +- **Calendario visual** con navegación mensual +- **Sistema de notas** con timestamps automáticos +- **Estadísticas detalladas**: promedio, total, racha limpia +- **Filtros de visualización**: semana, mes, todo +- **Exportación/Importación CSV** para backups +- **Personalización de emojis**: 11 rangos con 151 emojis disponibles +- **Tema oscuro/claro** automático +- **Diferenciación de consumo**: estándar, con weed, con polen +- **Historial ilimitado** con persistencia local + +### 🔒 Privacidad +- CERO recopilación de datos +- Almacenamiento 100% local (SharedPreferences) +- Sin analytics ni tracking +- Sin servicios de terceros + +### 🎨 Diseño +- Material Design 3 +- Interfaz intuitiva en español +- Emojis dinámicos según nivel de consumo +- Animaciones sutiles + +### 📱 Compatibilidad +- Android 7.0 (API 24) y superior +- Optimizado para teléfonos y tablets +- Modo vertical y horizontal + +### 🆓 Modelo +- Totalmente gratuita +- Sin anuncios +- Sin compras dentro de la app +- Código abierto (GPL-3.0) + +--- + +## [Unreleased] + +### 🚀 Próximas Funcionalidades (v1.2.0+) +- [ ] Internacionalización (inglés) +- [ ] Gráficas visuales de tendencias +- [ ] Backup automático en Google Drive +- [ ] Recordatorios programables +- [ ] Modo privacidad con PIN/huella +- [ ] Widgets adicionales (tamaños variados) +- [ ] Temas de color personalizables +- [ ] Exportación a PDF + +--- + +## Tipos de Cambios + +- **✨ Añadido** - Nuevas funcionalidades +- **🔧 Cambiado** - Cambios en funcionalidades existentes +- **❌ Deprecado** - Funcionalidades que se eliminarán pronto +- **🗑️ Eliminado** - Funcionalidades eliminadas +- **🐛 Corregido** - Corrección de bugs +- **🔒 Seguridad** - Parches de seguridad + +--- + +[1.1.0]: https://github.com/D4vRAM369/CBDcounter/compare/v1.0...v1.1.0 +[1.0.0]: https://github.com/D4vRAM369/CBDcounter/releases/tag/v1.0 +[Unreleased]: https://github.com/D4vRAM369/CBDcounter/compare/v1.1.0...HEAD diff --git a/PLAY_STORE_DESCRIPTION.md b/PLAY_STORE_DESCRIPTION.md new file mode 100644 index 0000000..d4b894a --- /dev/null +++ b/PLAY_STORE_DESCRIPTION.md @@ -0,0 +1,252 @@ +# Descripciones para Google Play Store + +## DESCRIPCIÓN CORTA (80 caracteres) + +**Opción 1 (76 caracteres):** +``` +Rastrea tu consumo de CBD con estadísticas, widget y total privacidad +``` + +**Opción 2 (79 caracteres):** +``` +Contador de CBD: seguimiento diario, calendario, notas y emojis personalizables +``` + +**Opción 3 (73 caracteres - RECOMENDADA):** +``` +Tracking personal de CBD con estadísticas, widget y backup de datos +``` + +--- + +## DESCRIPCIÓN LARGA (Hasta 4000 caracteres) + +``` +🌿 CBDCounter - Tu Compañero Personal de Seguimiento de CBD + +CBDCounter es una aplicación intuitiva y privada diseñada para ayudarte a registrar y analizar tu consumo de CBD de forma organizada. Perfecta para usuarios medicinales, terapéuticos o aquellos que simplemente buscan mantener un control consciente de su uso. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✨ CARACTERÍSTICAS PRINCIPALES + +📊 SEGUIMIENTO DIARIO SIMPLE +• Contador intuitivo con botones +1 y -1 +• Reset diario para empezar cada día desde cero +• Registro automático de timestamp en cada toma +• Sistema de notas para contexto adicional + +🏠 WIDGET DE PANTALLA PRINCIPAL +• Acceso rápido sin abrir la app +• Visualiza tu contador actual de un vistazo +• Actualización automática en tiempo real +• Emojis dinámicos según nivel de consumo + +📅 CALENDARIO VISUAL +• Vista mensual completa con emojis personalizados +• Navegación entre meses (anterior/siguiente) +• Identificación visual del día actual +• Leyenda dinámica de rangos de consumo + +📈 ESTADÍSTICAS DETALLADAS +• Promedio de consumo (semanal/mensual/total) +• Total acumulado por período +• Racha de días "limpios" (sin consumo) +• Filtros por semana, mes o historial completo + +📝 SISTEMA DE NOTAS +• Añade contexto a cada día +• Diferencia entre tipos: estándar, con weed, con polen +• Timestamps automáticos (HH:mm) +• Edición y borrado fácil + +🎨 PERSONALIZACIÓN TOTAL +• 11 rangos de emojis configurables (0, 1-2, 3-4, 5, 6, 7, 8, 9, 10, 11, 12+) +• Selector con 151 emojis disponibles +• Reset a valores por defecto cuando quieras +• Tema oscuro y claro automático + +💾 BACKUP Y PORTABILIDAD +• Exporta todos tus datos a CSV +• Formato compatible con Excel, Google Sheets +• Importa datos desde backups previos +• Control total sobre tus datos + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🔒 PRIVACIDAD ABSOLUTA + +Tu privacidad es nuestra prioridad #1: + +✅ CERO recopilación de datos personales +✅ TODO se guarda localmente en tu dispositivo +✅ NO enviamos información a servidores +✅ NO usamos analytics ni tracking +✅ NO compartimos nada con terceros +✅ NO requiere cuenta ni registro +✅ NO hay publicidad + +Tus datos son SOLO TUYOS. Punto. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +💡 CASOS DE USO IDEALES + +🏥 USO MEDICINAL +Perfecto para pacientes que necesitan: +• Tracking preciso de dosis +• Historial médico detallado +• Identificación de patrones +• Documentación para profesionales de la salud + +🧘 BIENESTAR PERSONAL +Ideal para quienes buscan: +• Control consciente del consumo +• Reducción gradual +• Identificar factores desencadenantes +• Mantener objetivos de uso responsable + +📊 ANÁLISIS DE PATRONES +Útil para: +• Detectar tendencias de consumo +• Evaluar efectividad de tratamientos +• Comparar períodos temporales +• Tomar decisiones informadas + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🛡️ DESCARGO DE RESPONSABILIDAD + +⚠️ INFORMACIÓN IMPORTANTE: + +Esta aplicación es una HERRAMIENTA DE TRACKING PERSONAL y: + +• NO es un dispositivo médico +• NO proporciona consejo médico, diagnóstico o tratamiento +• NO sustituye la consulta con profesionales de la salud +• NO promueve el consumo de sustancias +• NO facilita la compra, venta ni distribución de productos +• Solo sirve para registro personal bajo tu responsabilidad + +Consulta SIEMPRE con un médico u otro profesional de la salud calificado antes de tomar decisiones relacionadas con tu salud. + +Es TU RESPONSABILIDAD verificar la legalidad del CBD en tu jurisdicción. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🌍 ACCESIBILIDAD Y COMPATIBILIDAD + +• Compatible con Android 7.0 (Nougat) y superior +• Optimizada para teléfonos y tablets +• Interfaz en español +• Diseño Material Design 3 +• Modo oscuro automático según sistema +• Tamaños de texto adaptables +• Accesibilidad con TalkBack + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🆓 TOTALMENTE GRATUITA + +• Sin compras dentro de la app +• Sin suscripciones +• Sin anuncios +• Sin costos ocultos +• Todas las funciones desbloqueadas + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +👨‍💻 DESARROLLO INDEPENDIENTE + +CBDCounter es desarrollada por un desarrollador independiente español (D4vRAM) comprometido con: + +✅ Código de calidad +✅ Privacidad del usuario +✅ Actualizaciones regulares +✅ Soporte responsive +✅ Escuchar feedback de la comunidad + +🔓 Proyecto de código abierto disponible en GitHub para transparencia total. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📋 PERMISOS + +La app solo solicita 1 permiso: + +• RECEIVE_BOOT_COMPLETED: Para restaurar el widget después de reiniciar tu dispositivo. NO se usa para tracking ni monitoreo. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🆕 NOVEDADES EN V1.0.0 + +🎉 Lanzamiento inicial con: +• Contador diario intuitivo +• Widget de pantalla principal +• Calendario con emojis visuales +• Sistema completo de estadísticas +• Notas con timestamps +• Import/Export CSV +• 151 emojis personalizables +• Tema oscuro/claro +• Historial ilimitado + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📞 SOPORTE Y CONTACTO + +¿Preguntas? ¿Sugerencias? ¿Bugs? +Escríbeme a: d4vram369@gmail.com + +Respondo personalmente a todos los mensajes en ~7 días. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⭐ Si CBDCounter te resulta útil, ¡déjanos una reseña! Tu feedback ayuda a mejorar la app y a que más personas la descubran. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🏷️ PALABRAS CLAVE + +#CBD #Tracking #Contador #Salud #Bienestar #Privacidad #Widget #Estadísticas #Calendario #Notas #Backup #OpenSource #SinAnuncios #Gratuita #Medicinal #Terapéutico + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +© 2025 D4vRAM. Desarrollado con ❤️ desde Gran Canaria 🇮🇨 (España) + +Versión: 1.0.0 +Última actualización: Noviembre 2025 +``` + +**CARACTERES TOTALES:** 3,947 (dentro del límite de 4,000) ✅ + +--- + +## CATEGORÍA SUGERIDA + +**Principal:** Salud y bienestar +**Alternativa:** Estilo de vida + +--- + +## ETIQUETAS (Tags) + +``` +cbd, contador, tracking, salud, bienestar, privacidad, widget, estadísticas, calendario, medicinal +``` + +--- + +## NOTAS PARA LA FICHA + +**Clasificación de contenido:** +- App diseñada para mayores de 16/18 años +- Referencias a consumo de sustancias (CBD/cannabis) +- Uso educativo y de seguimiento personal +- NO facilita compra ni venta + +**Título de la app (50 caracteres max):** +``` +CBDCounter - Tracking de Consumo +``` +(38 caracteres) ✅ diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md new file mode 100644 index 0000000..383e701 --- /dev/null +++ b/PRIVACY_POLICY.md @@ -0,0 +1,229 @@ +# Política de Privacidad de CBDCounter + +**Última actualización:** 10 de noviembre de 2025 + +**Desarrollador:** D4vRAM +**Contacto:** d4vram369@gmail.com + +--- + +## 1. Introducción + +CBDCounter ("la Aplicación") es una herramienta de seguimiento personal desarrollada por D4vRAM. Esta Política de Privacidad describe cómo manejamos la información en relación con el uso de la Aplicación. + +**En resumen:** Esta aplicación NO recopila, transmite, almacena en servidores ni comparte ningún dato personal. + +--- + +## 2. Información que Recopilamos + +**CBDCounter NO recopila ningún dato personal de los usuarios.** + +La Aplicación no: +- ❌ Recopila nombres, correos electrónicos o información de contacto +- ❌ Recopila datos de ubicación +- ❌ Accede a tu lista de contactos, fotos o archivos +- ❌ Rastrea tu actividad fuera de la app +- ❌ Utiliza cookies o tecnologías de seguimiento +- ❌ Recopila identificadores de dispositivo únicos +- ❌ Transmite datos a servidores externos + +--- + +## 3. Datos Almacenados Localmente + +Todos los datos que generes usando CBDCounter se almacenan **exclusivamente en tu dispositivo** mediante la tecnología SharedPreferences de Android: + +### Datos almacenados localmente: +- **Contadores diarios:** Número de consumos registrados por fecha +- **Notas personales:** Texto que escribas para cada día +- **Emojis personalizados:** Configuraciones visuales que hayas modificado +- **Preferencias de tema:** Modo oscuro/claro +- **Estado del disclaimer:** Indicador de que aceptaste el aviso legal + +### Características importantes: +✅ Estos datos **NUNCA** salen de tu dispositivo +✅ NO se sincronizan con ningún servidor +✅ NO son accesibles por el desarrollador +✅ Son eliminados automáticamente si desinstalas la app + +--- + +## 4. Función de Exportación CSV + +La Aplicación permite exportar tus datos a un archivo CSV para crear copias de seguridad personales. + +**Importante:** +- Tú controlas este archivo exportado +- El archivo se guarda en tu dispositivo +- Puedes compartirlo manualmente mediante las opciones estándar de Android +- El desarrollador NO tiene acceso a este archivo +- Tú eres responsable de cómo compartes o almacenas este archivo + +--- + +## 5. Permisos de Android + +La Aplicación solicita los siguientes permisos: + +### `RECEIVE_BOOT_COMPLETED` +**Propósito:** Restaurar el widget de pantalla principal después de reiniciar el dispositivo. +**Uso:** Solo se utiliza para actualizar el widget, no para rastrear ni monitorear actividad. + +**Ningún otro permiso es solicitado o utilizado.** + +--- + +## 6. Servicios de Terceros + +CBDCounter **NO utiliza**: +- ❌ Servicios de analytics (Google Analytics, Firebase, etc.) +- ❌ Redes publicitarias +- ❌ Sistemas de tracking o métricas +- ❌ SDKs de terceros (excepto AndroidX y Material Design de Google, que son librerías estándar de UI sin capacidad de recopilación de datos) + +--- + +## 7. Seguridad de los Datos + +Dado que todos los datos se almacenan localmente en tu dispositivo: + +- La seguridad depende de la protección de tu dispositivo (PIN, huella, etc.) +- En dispositivos rooteados, aplicaciones con permisos de root podrían acceder a los datos +- Recomendamos usar las funciones de seguridad nativas de Android + +**Nota:** Si tu dispositivo es compartido con otras personas, ellas podrían acceder a la Aplicación y ver tus datos. + +--- + +## 8. Privacidad de Menores + +CBDCounter no está dirigida a menores de 13 años. Dado que no recopilamos ningún dato, no recopilamos intencionalmente información de niños menores de 13 años. + +--- + +## 9. Cambios a esta Política + +Podemos actualizar esta Política de Privacidad ocasionalmente. Te notificaremos cualquier cambio publicando la nueva Política en esta página y actualizando la fecha de "Última actualización". + +**Es tu responsabilidad revisar esta Política periódicamente.** El uso continuado de la Aplicación después de cambios constituye aceptación de dichos cambios. + +--- + +## 10. Limitaciones y Descargo de Responsabilidad + +### CBDCounter NO es un dispositivo médico + +- Esta Aplicación NO proporciona consejo médico, diagnóstico o tratamiento +- NO está destinada a sustituir la consulta con un profesional de la salud +- El contenido es solo para fines informativos y de seguimiento personal +- Siempre consulta con un médico u otro profesional de la salud calificado sobre cualquier pregunta relacionada con tu salud + +### Uso de la Aplicación + +- El uso de CBDCounter es bajo tu propio riesgo +- La Aplicación se proporciona "tal cual" sin garantías de ningún tipo +- El desarrollador no se hace responsable del uso que hagas de los datos rastreados +- No promovemos, facilitamos ni apoyamos actividades ilegales + +### Legalidad del CBD + +- Es responsabilidad del usuario verificar la legalidad del CBD en su jurisdicción +- El desarrollador no asume responsabilidad por el uso de la Aplicación en jurisdicciones donde el CBD es ilegal +- Esta Aplicación NO facilita la compra, venta ni distribución de CBD u otras sustancias + +--- + +## 11. Tus Derechos + +Dado que no recopilamos ni almacenamos datos fuera de tu dispositivo: + +### Derecho de acceso +✅ Todos tus datos están siempre accesibles en la Aplicación + +### Derecho de rectificación +✅ Puedes editar tus notas y datos en cualquier momento + +### Derecho de eliminación +✅ Simplemente desinstala la Aplicación o usa la función de "reset" en ajustes + +### Derecho de portabilidad +✅ Usa la función "Exportar CSV" para obtener tus datos en formato portable + +### Derecho de oposición +✅ No aplica, ya que no procesamos tus datos + +--- + +## 12. Cumplimiento Legal + +Esta Política de Privacidad cumple con: +- **RGPD** (Reglamento General de Protección de Datos de la UE) +- **LOPD** (Ley Orgánica de Protección de Datos de España) +- **Políticas de Privacidad de Google Play** + +--- + +## 13. Transferencias Internacionales + +No aplicable. Tus datos nunca abandonan tu dispositivo. + +--- + +## 14. Retención de Datos + +Los datos se retienen mientras: +- Mantengas la Aplicación instalada en tu dispositivo +- No uses la función de reset o borrado manual + +Los datos se eliminan automáticamente cuando: +- Desinstalas la Aplicación +- Borras manualmente los datos desde configuración de Android +- Reseteas tu dispositivo a valores de fábrica + +--- + +## 15. Base Legal para el Procesamiento (RGPD) + +No aplicable, ya que no procesamos datos personales fuera de tu dispositivo. + +El almacenamiento local en tu dispositivo está bajo tu control exclusivo. + +--- + +## 16. Contacto + +Si tienes preguntas, comentarios o inquietudes sobre esta Política de Privacidad: + +**Email:** d4vram369@gmail.com +**Método de contacto preferido:** Email +**Tiempo de respuesta esperado:** 7 días hábiles + +--- + +## 17. Jurisdicción + +Esta Política de Privacidad se rige por las leyes de España. + +--- + +## 18. Idioma + +Esta Política está disponible en español. En caso de discrepancia entre traducciones, la versión en español prevalecerá. + +--- + +## Resumen Ejecutivo (TL;DR) + +✅ **NO recopilamos datos** +✅ **Todo se guarda en tu dispositivo** +✅ **NO compartimos nada con nadie** +✅ **NO usamos analytics ni publicidad** +✅ **Tú controlas 100% de tus datos** +✅ **Desinstalar = eliminar todo** + +--- + +**Fecha de entrada en vigor:** 10 de noviembre de 2025 + +© 2025 D4vRAM. Todos los derechos reservados. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f323965..cc58f8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,8 +1,18 @@ +import java.util.Properties +import java.io.FileInputStream + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } +// Leer credenciales del keystore de forma segura +val keystorePropertiesFile = rootProject.file("keystore.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + android { namespace = "com.d4vram.cbdcounter" compileSdk = 34 @@ -11,15 +21,29 @@ android { applicationId = "com.d4vram.cbdcounter" minSdk = 24 targetSdk = 34 - versionCode = 1 - versionName = "1.1" + versionCode = 2 + versionName = "1.1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + // Configuración de firma digital + signingConfigs { + create("release") { + if (keystorePropertiesFile.exists()) { + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + storeFile = file(keystoreProperties.getProperty("storeFile")) + storePassword = keystoreProperties.getProperty("storePassword") + } + } + } + buildTypes { release { - isMinifyEnabled = false + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = true + isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -49,4 +73,4 @@ dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") -} \ No newline at end of file +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..835be83 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,21 +1,32 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html +# Mantener Activities y Services + -keep public class * extends android.app.Activity + -keep public class * extends android.app.Application + -keep public class * extends android.appwidget.AppWidgetProvider -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} + # Mantener clases de AndroidX + -keep class androidx.** { *; } + -dontwarn androidx.** -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable + # Mantener Material Design + -keep class com.google.android.material.** { *; } + -dontwarn com.google.android.material.** -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file + # Mantener data classes (para no romper tus HistoryItem, etc) + -keepclassmembers class ** { + @kotlin.jvm.JvmField public ; + } + + # Mantener atributos para stack traces legibles + -keepattributes SourceFile,LineNumberTable + -renamesourcefileattribute SourceFile + + # Mantener enums (para InfusionType, ViewMode, etc) + -keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); + } + + # Mantener Parcelables si los usas en el futuro + -keepclassmembers class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b947fea..2a5b729 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + + + + + + + @@ -29,6 +49,10 @@ + + + + { + updateAllWidgets(context) + } } } @@ -64,7 +133,7 @@ class CBDWidgetProvider : AppWidgetProvider() { // Obtener contador actual val count = getCurrentCount(context) val date = getCurrentDateDisplay() - val emoji = getEmoji(count) + val emoji = EmojiUtils.emojiForCount(count, context) // Actualizar vistas views.setTextViewText(R.id.widget_counter, count.toString()) @@ -133,20 +202,4 @@ class CBDWidgetProvider : AppWidgetProvider() { val formatter = SimpleDateFormat("dd/MM", Locale.getDefault()) return formatter.format(Date()) } - - private fun getEmoji(count: Int): String { - return when { - count == 0 -> "😌" - count <= 2 -> "🙂" - count <= 4 -> "😄" - count <= 5 -> "🫠" - count <= 6 -> "🤔" - count <= 7 -> "🙄" - count <= 8 -> "😶‍🌫️" - count <= 9 -> "🫡" - count <= 10 -> "🫥" - count <= 11 -> "⛔️" - else -> "💀" - } - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/d4vram/cbdcounter/EmojiSettingsActivity.kt b/app/src/main/java/com/d4vram/cbdcounter/EmojiSettingsActivity.kt new file mode 100644 index 0000000..99154b1 --- /dev/null +++ b/app/src/main/java/com/d4vram/cbdcounter/EmojiSettingsActivity.kt @@ -0,0 +1,245 @@ +package com.d4vram.cbdcounter + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.button.MaterialButton + +/** + * Actividad para personalizar los emojis según rangos de consumo + */ +class EmojiSettingsActivity : AppCompatActivity() { + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: EmojiRangeAdapter + private lateinit var resetButton: MaterialButton + + // Lista de rangos con sus emojis por defecto + private val emojiRanges = listOf( + EmojiRange(0, "😌", R.color.green_safe, "0"), + EmojiRange(1, "🙂", R.color.green_safe, "1-2"), + EmojiRange(3, "😄", R.color.yellow_warning, "3-4"), + EmojiRange(5, "🫠", R.color.yellow_warning, "5"), + EmojiRange(6, "🤔", R.color.orange_danger, "6"), + EmojiRange(7, "🙄", R.color.orange_danger, "7"), + EmojiRange(8, "😶‍🌫️", R.color.orange_danger, "8"), + EmojiRange(9, "🫡", R.color.red_critical, "9"), + EmojiRange(10, "🫥", R.color.red_critical, "10"), + EmojiRange(11, "⛔️", R.color.red_critical, "11"), + EmojiRange(12, "💀", R.color.primary_purple, "12+") + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_emoji_settings) + + // Configurar toolbar + val toolbar = findViewById(R.id.settingsToolbar) + toolbar.setNavigationOnClickListener { finish() } + + // Configurar RecyclerView + recyclerView = findViewById(R.id.emojiRangesRecycler) + recyclerView.layoutManager = LinearLayoutManager(this) + + // Cargar emojis guardados o usar los por defecto + val currentEmojis = loadCustomEmojis() + adapter = EmojiRangeAdapter(emojiRanges, currentEmojis) { range, newEmoji -> + // Callback cuando se cambia un emoji + saveCustomEmoji(range.count, newEmoji) + } + recyclerView.adapter = adapter + + // Configurar botón de reset + resetButton = findViewById(R.id.resetButton) + resetButton.setOnClickListener { + showResetConfirmationDialog() + } + } + + /** + * Carga los emojis personalizados guardados en SharedPreferences + */ + private fun loadCustomEmojis(): Map { + val prefs = getSharedPreferences("emoji_prefs", MODE_PRIVATE) + val customEmojis = mutableMapOf() + + for (range in emojiRanges) { + val savedEmoji = prefs.getString("emoji_${range.count}", null) + if (savedEmoji != null) { + customEmojis[range.count] = savedEmoji + } + } + + return customEmojis + } + + /** + * Guarda un emoji personalizado en SharedPreferences + */ + private fun saveCustomEmoji(count: Int, emoji: String) { + val prefs = getSharedPreferences("emoji_prefs", MODE_PRIVATE) + prefs.edit().putString("emoji_$count", emoji).apply() + } + + /** + * Borra todos los emojis personalizados (volver a por defecto) + */ + private fun resetToDefaults() { + val prefs = getSharedPreferences("emoji_prefs", MODE_PRIVATE) + prefs.edit().clear().apply() + + // Recargar el adapter + adapter.resetToDefaults() + } + + /** + * Muestra diálogo de confirmación antes de resetear + */ + private fun showResetConfirmationDialog() { + AlertDialog.Builder(this) + .setTitle("Restaurar valores por defecto") + .setMessage("¿Estás seguro de que quieres restaurar todos los emojis a sus valores originales?") + .setPositiveButton("Sí") { _, _ -> + resetToDefaults() + } + .setNegativeButton("Cancelar", null) + .show() + } + + /** + * Muestra selector de emojis con categorías + */ + private fun showEmojiPicker(currentEmoji: String, onEmojiSelected: (String) -> Unit) { + // Lista AMPLIADA de emojis disponibles organizados por categoría + val emojis = listOf( + // Caras positivas y neutrales + "😌", "🙂", "😊", "😀", "😃", "😄", "😁", "😆", "😅", "🤣", + "😂", "🙃", "😉", "😇", "🤩", "☺️", "🥲", "😋", "😛", "😜", "🤪", "😝", + + // Caras pensativas y confundidas + "🤔", "🤨", "😐", "😑", "😶", "🙄", "😣", "😥", "😮", "😯", "😪", "😫", "🥱", "😴", "😌", "🤤", + + // Caras alteradas y mareadas + "🫠", "😵", "😵‍💫", "🤯", "🥴", "😲", + + // Caras serias y militares + "🫡", "😬", "🫨", "🫥", + + // Caras negativas y enfadadas + "😞", "😔", "😟", "😕", "🙁", "☹️", "😰", "😨", "😧", "😦", "😈", + "👿", "💀", "☠️", "👻", "👽", "👾", + + // Gestos y manos + "👍", "👎", "🤞", "✌️", "👌", "🤌", "🤏", "✋", "🤚", + + // Objetos y símbolos relacionados con CBD/THC + "🌿", "🍀", "🌱", "🌾", "🪴", "🍃", + + // Símbolos de advertencia y estado + "⚠️", "🚫", "⛔️", "🔞", "📵", "🔕", "❌", "⭕️", "❗️", "❓", + + // Colores y formas + "🟢", "🟡", "🟠", "🔴", "🟣", "🔵", "🟤", "⚫️", "⚪️", "🟥", + "🟧", "🟨", "🟩", "🟦", "🟪", "🟫", "⬛️", "⬜️", "◼️", "◻️", + "◾️", "◽️", "▪️", "▫️", "🔶", "🔷", "🔸", "🔹", "🔺", "🔻", + + // Símbolos adicionales + "💚", "💛", "🧡", "❤️", "💜", "💙", "🖤", "🤍", "🤎", "💯", + "💥", "💫", "⭐️", "🌟", "✨", "⚡️", "🔥", "💧", "💦", "☁️", + + // Números + "0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", + ) + + val emojiArray = emojis.toTypedArray() + var selectedIndex = emojis.indexOf(currentEmoji).takeIf { it >= 0 } ?: 0 + + AlertDialog.Builder(this) + .setTitle("Selecciona un emoji (${emojis.size} disponibles)") + .setSingleChoiceItems(emojiArray, selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton("Aceptar") { _, _ -> + onEmojiSelected(emojis[selectedIndex]) + } + .setNegativeButton("Cancelar", null) + .show() + } + + // ======================================== + // Clases de datos y Adapter + // ======================================== + + /** + * Representa un rango de consumo con su emoji + */ + data class EmojiRange( + val count: Int, // Valor representativo (0, 1, 3, 5, etc.) + val defaultEmoji: String, // Emoji por defecto + val colorRes: Int, // Color del indicador + val rangeText: String // Texto a mostrar ("0", "1-2", "12+") + ) + + /** + * Adapter del RecyclerView + */ + inner class EmojiRangeAdapter( + private val ranges: List, + private var customEmojis: Map, + private val onEmojiChanged: (EmojiRange, String) -> Unit + ) : RecyclerView.Adapter() { + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val colorIndicator: View = view.findViewById(R.id.colorIndicator) + val rangeText: TextView = view.findViewById(R.id.rangeText) + val emojiText: TextView = view.findViewById(R.id.emojiText) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_emoji_range, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val range = ranges[position] + val currentEmoji = customEmojis[range.count] ?: range.defaultEmoji + + // Configurar vistas + holder.rangeText.text = range.rangeText + holder.emojiText.text = currentEmoji + holder.colorIndicator.setBackgroundColor( + ContextCompat.getColor(holder.itemView.context, range.colorRes) + ) + + // Click en el emoji para cambiarlo + holder.emojiText.setOnClickListener { + showEmojiPicker(currentEmoji) { newEmoji -> + // Actualizar el mapa de emojis personalizados + val mutableCustom = customEmojis.toMutableMap() + mutableCustom[range.count] = newEmoji + customEmojis = mutableCustom + + // Notificar cambio + notifyItemChanged(position) + onEmojiChanged(range, newEmoji) + } + } + } + + override fun getItemCount() = ranges.size + + fun resetToDefaults() { + customEmojis = emptyMap() + notifyDataSetChanged() + } + } +} diff --git a/app/src/main/java/com/d4vram/cbdcounter/EmojiUtils.kt b/app/src/main/java/com/d4vram/cbdcounter/EmojiUtils.kt new file mode 100644 index 0000000..d46e798 --- /dev/null +++ b/app/src/main/java/com/d4vram/cbdcounter/EmojiUtils.kt @@ -0,0 +1,58 @@ +package com.d4vram.cbdcounter + +import android.content.Context + +object EmojiUtils { + + /** + * Obtiene el emoji para un conteo, considerando personalizaciones del usuario + */ + fun emojiForCount(count: Int, context: Context? = null): String { + // Si hay contexto, intentar cargar emoji personalizado + if (context != null) { + val customEmoji = getCustomEmoji(context, count) + if (customEmoji != null) { + return customEmoji + } + } + + // Emojis por defecto + return when { + count == 0 -> "😌" + count <= 2 -> "🙂" + count <= 4 -> "😄" + count <= 5 -> "🫠" + count <= 6 -> "🤔" + count <= 7 -> "🙄" + count <= 8 -> "😶‍🌫️" + count <= 9 -> "🫡" + count <= 10 -> "🫥" + count <= 11 -> "⛔️" + else -> "💀" + } + } + + /** + * Obtiene el emoji personalizado guardado para un conteo específico + */ + private fun getCustomEmoji(context: Context, count: Int): String? { + val prefs = context.getSharedPreferences("emoji_prefs", Context.MODE_PRIVATE) + + // Mapear el count al rango correcto + val rangeKey = when { + count == 0 -> 0 + count in 1..2 -> 1 + count in 3..4 -> 3 + count == 5 -> 5 + count == 6 -> 6 + count == 7 -> 7 + count == 8 -> 8 + count == 9 -> 9 + count == 10 -> 10 + count == 11 -> 11 + else -> 12 // 12+ + } + + return prefs.getString("emoji_$rangeKey", null) + } +} diff --git a/app/src/main/java/com/d4vram/cbdcounter/MainActivity.kt b/app/src/main/java/com/d4vram/cbdcounter/MainActivity.kt index 2b4ede0..014f47e 100644 --- a/app/src/main/java/com/d4vram/cbdcounter/MainActivity.kt +++ b/app/src/main/java/com/d4vram/cbdcounter/MainActivity.kt @@ -17,12 +17,16 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.tabs.TabLayout import com.google.android.material.switchmaterial.SwitchMaterial +import com.google.android.material.button.MaterialButton +import com.google.android.material.chip.Chip +import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.io.BufferedReader import java.io.File import java.io.InputStreamReader @@ -37,10 +41,13 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { private lateinit var dateText: TextView private lateinit var emojiText: TextView private lateinit var addButton: Button + private lateinit var addInfusedButton: MaterialButton + private lateinit var statsButton: Chip private lateinit var subtractButton: Button private lateinit var resetButton: Button private lateinit var exportButton: ImageButton private lateinit var importButton: ImageButton + private lateinit var settingsButton: ImageButton // Botón switch para cambiar el tema private lateinit var themeSwitch: SwitchMaterial @@ -61,12 +68,28 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { private val displayedHistoryData = ArrayList() private var currentViewMode = ViewMode.WEEK + private val importMimeTypes = arrayOf( + "text/csv", + "text/comma-separated-values", + "application/csv", + "application/vnd.ms-excel", + "text/plain" + ) + private val importCsvLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> uri?.let { handleImportCsv(it) } } enum class ViewMode { WEEK, MONTH, ALL } + private enum class InfusionType( + @StringRes val labelRes: Int, + @StringRes val feedbackRes: Int, + val icon: String + ) { + WEED(R.string.weed_option, R.string.cbd_infused_added_weed, "\uD83D\uDFE2"), + POLEM(R.string.polem_option, R.string.cbd_infused_added_polem, "\uD83D\uDFE4") + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,6 +104,15 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { updateDisplay() updateHistoryView() updateStats() + + // Mostrar disclaimer médico en el primer uso + showDisclaimerIfNeeded() + } + + override fun onResume() { + super.onResume() + // Actualizar emoji cuando se vuelve de EmojiSettingsActivity + updateDisplay() } private fun initViews() { @@ -89,11 +121,14 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { dateText = findViewById(R.id.dateText) emojiText = findViewById(R.id.emojiText) addButton = findViewById(R.id.addButton) + addInfusedButton = findViewById(R.id.addInfusedButton) + statsButton = findViewById(R.id.statsButton) subtractButton = findViewById(R.id.subtractButton) resetButton = findViewById(R.id.resetButton) exportButton = findViewById(R.id.exportButton) importButton = findViewById(R.id.importButton) - themeSwitch = findViewById(R.id.themeSwitch) + settingsButton = findViewById(R.id.settingsButton) + themeSwitch = findViewById(R.id.themeSwitch) // Estado inicial del switch según el tema actual val isNight = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK @@ -269,38 +304,24 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { counterText.setTextColor(ContextCompat.getColor(this, color)) } - private fun getEmoji(count: Int): String { - return when { - count == 0 -> "😌" - count <= 2 -> "🙂" - count <= 4 -> "😄" - count <= 5 -> "🫠" - count <= 6 -> "🤔" - count <= 7 -> "🙄" - count <= 8 -> "😶‍🌫️" - count <= 9 -> "🫡" - count <= 10 -> "🫥" - count <= 11 -> "⛔️" - else -> "💀" - } - } + private fun getEmoji(count: Int): String = EmojiUtils.emojiForCount(count, this) private fun setupClickListeners() { - addButton.setOnClickListener { - currentCount++ - updateDisplay() - appendTimestampToTodayNote() - saveData() - animateCounter(1.1f) - showFeedback("CBD agregado", false) + addButton.setOnClickListener { registerStandardIntake() } + addInfusedButton.setOnClickListener { showInfusionDialog() } + statsButton.setOnClickListener { openStatsCalendar() } + settingsButton.setOnClickListener { + // Abrir pantalla de personalización de emojis + startActivity(Intent(this, EmojiSettingsActivity::class.java)) } subtractButton.setOnClickListener { if (currentCount > 0) { currentCount-- updateDisplay() + removeLastEntryFromTodayNote() // 🎯 Borrar último timestamp saveData() animateCounter(0.9f) - showFeedback("CBD restado", true) + showFeedback(getString(R.string.cbd_subtracted), true) } } resetButton.setOnClickListener { @@ -319,7 +340,15 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { exportButton.setOnClickListener { exportCsv() } importButton.setOnClickListener { - importCsvLauncher.launch(arrayOf("text/csv", "text/plain")) + // Mostrar diálogo de confirmación antes de importar + MaterialAlertDialogBuilder(this) + .setTitle("⚠️ Importar datos") + .setMessage("Esto BORRARÁ todos tus datos actuales (historial, notas y emojis personalizados) y los reemplazará con los del archivo CSV.\n\n¿Estás seguro de continuar?") + .setPositiveButton("Sí, importar") { _, _ -> + importCsvLauncher.launch(importMimeTypes) + } + .setNegativeButton("Cancelar", null) + .show() } } @@ -507,11 +536,85 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { return builder.toString() } - private fun appendTimestampToTodayNote() { + private fun registerStandardIntake() { + val entry = "🔸 ${getCurrentTimestamp()}" + registerIntake(entry, getString(R.string.cbd_added)) + } + + private fun showInfusionDialog() { + val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_infusion_choice, null) + val weedButton = dialogView.findViewById(R.id.weedButton) + val polemButton = dialogView.findViewById(R.id.polemButton) + + val dialog = MaterialAlertDialogBuilder(this) + .setView(dialogView) + .create() + + weedButton.text = "${InfusionType.WEED.icon} ${getString(InfusionType.WEED.labelRes)}" + polemButton.text = "${InfusionType.POLEM.icon} ${getString(InfusionType.POLEM.labelRes)}" + + weedButton.setOnClickListener { + handleInfusionSelection(InfusionType.WEED) + dialog.dismiss() + } + polemButton.setOnClickListener { + handleInfusionSelection(InfusionType.POLEM) + dialog.dismiss() + } + + dialog.show() + dialog.window?.setBackgroundDrawableResource(android.R.color.transparent) + } + + private fun handleInfusionSelection(type: InfusionType) { + val label = getString(type.labelRes) + val suffix = getString(R.string.infusion_note_suffix, label) + val entry = "${type.icon} ${getCurrentTimestamp()}$suffix" + registerIntake(entry, getString(type.feedbackRes)) + } + + private fun registerIntake(entry: String, feedbackMessage: String) { + currentCount++ + updateDisplay() + appendEntryToTodayNote(entry) + saveData() + animateCounter(1.1f) + showFeedback(feedbackMessage, false) + } + + private fun getCurrentTimestamp(): String = + SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) + + /** + * Muestra el disclaimer médico la primera vez que se abre la app. + * Requerido por las políticas de Google Play para apps relacionadas con sustancias. + */ + private fun showDisclaimerIfNeeded() { + val disclaimerAccepted = sharedPrefs.getBoolean("disclaimer_accepted", false) + if (!disclaimerAccepted) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.disclaimer_title) + .setMessage(R.string.disclaimer_message) + .setPositiveButton(R.string.disclaimer_accept) { _, _ -> + sharedPrefs.edit().putBoolean("disclaimer_accepted", true).apply() + } + .setNegativeButton(R.string.disclaimer_decline) { _, _ -> + // Si el usuario no acepta, cerrar la app + Toast.makeText( + this, + "Debes aceptar el aviso para usar la aplicación", + Toast.LENGTH_LONG + ).show() + finish() + } + .setCancelable(false) // No puede cancelar con el botón atrás + .show() + } + } + + private fun appendEntryToTodayNote(entry: String) { val today = getCurrentDateKey() - val timestamp = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) val currentNote = Prefs.getNote(this, today) - val entry = "🔸 $timestamp" val updatedNote = if (currentNote.isNullOrBlank()) { entry @@ -525,6 +628,35 @@ class MainActivity : AppCompatActivity(), NoteBottomSheet.Listener { Prefs.setNote(this, today, updatedNote) } + + private fun removeLastEntryFromTodayNote() { + val today = getCurrentDateKey() + val currentNote = Prefs.getNote(this, today) + + // Si no hay nota o está vacía, no hay nada que borrar + if (currentNote.isNullOrBlank()) return + + // Dividir la nota en líneas + val lines = currentNote.split("\n").toMutableList() + + // Eliminar la última línea + if (lines.isNotEmpty()) { + lines.removeAt(lines.lastIndex) + } + + // Si quedan líneas, unirlas de nuevo; si no, guardar null + val updatedNote = if (lines.isNotEmpty()) { + lines.joinToString("\n") + } else { + null + } + + Prefs.setNote(this, today, updatedNote) + } + + private fun openStatsCalendar() { + startActivity(Intent(this, StatsActivity::class.java)) + } } // Data class diff --git a/app/src/main/java/com/d4vram/cbdcounter/NoteBottomSheet.kt b/app/src/main/java/com/d4vram/cbdcounter/NoteBottomSheet.kt index ca676e4..dc7f614 100644 --- a/app/src/main/java/com/d4vram/cbdcounter/NoteBottomSheet.kt +++ b/app/src/main/java/com/d4vram/cbdcounter/NoteBottomSheet.kt @@ -28,8 +28,9 @@ class NoteBottomSheet : BottomSheetDialogFragment() { val btnSave = v.findViewById(R.id.btnSave) val btnDelete = v.findViewById(R.id.btnDelete) - tvTitle.text = "Nota del día $dateArg" - etNote.setText(Prefs.getNote(requireContext(), dateArg) ?: "") + tvTitle.text = getString(R.string.note_title_with_date, dateArg) + val initialNote = Prefs.getNote(requireContext(), dateArg) ?: "" + etNote.setText(initialNote) btnSave.setOnClickListener { val text = etNote.text?.toString()?.trim() diff --git a/app/src/main/java/com/d4vram/cbdcounter/StatsActivity.kt b/app/src/main/java/com/d4vram/cbdcounter/StatsActivity.kt new file mode 100644 index 0000000..8d66d2e --- /dev/null +++ b/app/src/main/java/com/d4vram/cbdcounter/StatsActivity.kt @@ -0,0 +1,242 @@ +package com.d4vram.cbdcounter + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.MaterialToolbar +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +class StatsActivity : AppCompatActivity() { + + private lateinit var toolbar: MaterialToolbar + private lateinit var monthLabel: TextView + private lateinit var prevMonthButton: ImageButton + private lateinit var nextMonthButton: ImageButton + private lateinit var calendarRecycler: RecyclerView + private lateinit var sharedPrefs: SharedPreferences + + // Referencias a los TextViews de la leyenda + private lateinit var legendEmoji0: TextView + private lateinit var legendEmoji1: TextView + private lateinit var legendEmoji3: TextView + private lateinit var legendEmoji5: TextView + private lateinit var legendEmoji6: TextView + private lateinit var legendEmoji7: TextView + private lateinit var legendEmoji8: TextView + private lateinit var legendEmoji9: TextView + private lateinit var legendEmoji10: TextView + private lateinit var legendEmoji11: TextView + private lateinit var legendEmoji12: TextView + + private val displayCalendar: Calendar = Calendar.getInstance() + private val monthFormat = SimpleDateFormat("LLLL yyyy", Locale("es", "ES")) + private val dateKeyFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + private val todayKey: String = dateKeyFormat.format(Date()) + private val calendarAdapter = CalendarAdapter() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_stats) + + toolbar = findViewById(R.id.statsToolbar) + monthLabel = findViewById(R.id.monthLabel) + prevMonthButton = findViewById(R.id.prevMonthButton) + nextMonthButton = findViewById(R.id.nextMonthButton) + calendarRecycler = findViewById(R.id.calendarRecycler) + + // Inicializar referencias a los TextViews de la leyenda + legendEmoji0 = findViewById(R.id.legendEmoji0) + legendEmoji1 = findViewById(R.id.legendEmoji1) + legendEmoji3 = findViewById(R.id.legendEmoji3) + legendEmoji5 = findViewById(R.id.legendEmoji5) + legendEmoji6 = findViewById(R.id.legendEmoji6) + legendEmoji7 = findViewById(R.id.legendEmoji7) + legendEmoji8 = findViewById(R.id.legendEmoji8) + legendEmoji9 = findViewById(R.id.legendEmoji9) + legendEmoji10 = findViewById(R.id.legendEmoji10) + legendEmoji11 = findViewById(R.id.legendEmoji11) + legendEmoji12 = findViewById(R.id.legendEmoji12) + + toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } + + sharedPrefs = getSharedPreferences("CBDCounter", Context.MODE_PRIVATE) + + calendarRecycler.layoutManager = GridLayoutManager(this, 7) + calendarRecycler.adapter = calendarAdapter + + prevMonthButton.setOnClickListener { + displayCalendar.add(Calendar.MONTH, -1) + updateCalendar() + } + + nextMonthButton.setOnClickListener { + displayCalendar.add(Calendar.MONTH, 1) + updateCalendar() + } + + updateCalendar() + updateLegend() // Cargar emojis dinámicamente al iniciar + } + + /** + * Se ejecuta cada vez que la Activity vuelve a primer plano + * Esto incluye cuando vuelves de EmojiSettingsActivity + */ + override fun onResume() { + super.onResume() + updateLegend() // Actualizar leyenda por si cambiaron los emojis + updateCalendar() // Actualizar calendario también + } + + private fun updateCalendar() { + displayCalendar.set(Calendar.DAY_OF_MONTH, 1) + val formattedMonth = monthFormat.format(displayCalendar.time) + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + monthLabel.text = formattedMonth + calendarAdapter.submit(buildCalendarCells()) + } + + /** + * Actualiza la leyenda de emojis con los valores personalizados del usuario + * Esta función lee desde SharedPreferences y actualiza cada TextView dinámicamente + */ + private fun updateLegend() { + // Definir los rangos con sus labels correspondientes + val legendData = listOf( + Triple(0, legendEmoji0, "0"), + Triple(1, legendEmoji1, "1-2"), + Triple(3, legendEmoji3, "3-4"), + Triple(5, legendEmoji5, "5"), + Triple(6, legendEmoji6, "6"), + Triple(7, legendEmoji7, "7"), + Triple(8, legendEmoji8, "8"), + Triple(9, legendEmoji9, "9"), + Triple(10, legendEmoji10, "10"), + Triple(11, legendEmoji11, "11"), + Triple(12, legendEmoji12, "12+") + ) + + // Actualizar cada TextView con el emoji correspondiente + for ((count, textView, rangeText) in legendData) { + val emoji = EmojiUtils.emojiForCount(count, this) + textView.text = "$emoji $rangeText" + } + } + + private fun buildCalendarCells(): List { + val items = mutableListOf() + val workingCalendar = displayCalendar.clone() as Calendar + + val firstDayOffset = mondayFirstDayOffset(workingCalendar.get(Calendar.DAY_OF_WEEK)) + repeat(firstDayOffset) { items.add(CalendarCell.Empty) } + + val daysInMonth = workingCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + val currentMonth = workingCalendar.get(Calendar.MONTH) + val currentYear = workingCalendar.get(Calendar.YEAR) + val todayCalendar = Calendar.getInstance() + + for (day in 1..daysInMonth) { + workingCalendar.set(Calendar.DAY_OF_MONTH, day) + val dateKey = dateKeyFormat.format(workingCalendar.time) + val prefKey = "count_$dateKey" + val hasData = sharedPrefs.contains(prefKey) + val count = sharedPrefs.getInt(prefKey, 0) + val emoji = if (hasData) EmojiUtils.emojiForCount(count, this) else "" + val isToday = dateKey == todayKey && + todayCalendar.get(Calendar.MONTH) == currentMonth && + todayCalendar.get(Calendar.YEAR) == currentYear + + items.add(CalendarCell.Day(day, emoji, hasData, isToday)) + } + + while (items.size % 7 != 0) { + items.add(CalendarCell.Empty) + } + + return items + } + + private fun mondayFirstDayOffset(dayOfWeek: Int): Int { + val adjusted = (dayOfWeek - Calendar.MONDAY) + return if (adjusted < 0) adjusted + 7 else adjusted + } + + private sealed class CalendarCell { + object Empty : CalendarCell() + data class Day( + val number: Int, + val emoji: String, + val hasData: Boolean, + val isToday: Boolean + ) : CalendarCell() + } + + private class CalendarAdapter : RecyclerView.Adapter() { + private val items = mutableListOf() + + fun submit(data: List) { + items.clear() + items.addAll(data) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_calendar_day, parent, false) + return CalendarViewHolder(view) + } + + override fun onBindViewHolder(holder: CalendarViewHolder, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount(): Int = items.size + + class CalendarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val dayContainer: View = itemView.findViewById(R.id.dayContainer) + private val dayNumber: TextView = itemView.findViewById(R.id.dayNumber) + private val dayEmoji: TextView = itemView.findViewById(R.id.dayEmoji) + + fun bind(cell: CalendarCell) { + when (cell) { + is CalendarCell.Empty -> { + dayNumber.text = "" + dayNumber.alpha = 1f + dayEmoji.text = "" + dayEmoji.visibility = View.INVISIBLE + dayContainer.background = null + } + is CalendarCell.Day -> { + dayNumber.text = cell.number.toString() + dayEmoji.text = cell.emoji + dayNumber.alpha = when { + cell.isToday -> 1f + cell.hasData -> 1f + else -> 0.45f + } + dayEmoji.visibility = if (cell.hasData && cell.emoji.isNotBlank()) View.VISIBLE else View.INVISIBLE + dayContainer.background = if (cell.isToday) { + androidx.appcompat.content.res.AppCompatResources.getDrawable( + itemView.context, + R.drawable.bg_calendar_today + ) + } else { + null + } + } + } + } + } + } +} diff --git a/app/src/main/res/drawable/bg_calendar_today.xml b/app/src/main/res/drawable/bg_calendar_today.xml new file mode 100644 index 0000000..aada880 --- /dev/null +++ b/app/src/main/res/drawable/bg_calendar_today.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/dialog_infusion_background.xml b/app/src/main/res/drawable/dialog_infusion_background.xml new file mode 100644 index 0000000..6ccb731 --- /dev/null +++ b/app/src/main/res/drawable/dialog_infusion_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 0000000..2f8d112 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 0000000..b1fe64a --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/legend_item_critical.xml b/app/src/main/res/drawable/legend_item_critical.xml new file mode 100644 index 0000000..9780920 --- /dev/null +++ b/app/src/main/res/drawable/legend_item_critical.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/legend_item_danger.xml b/app/src/main/res/drawable/legend_item_danger.xml new file mode 100644 index 0000000..7522adb --- /dev/null +++ b/app/src/main/res/drawable/legend_item_danger.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/legend_item_high.xml b/app/src/main/res/drawable/legend_item_high.xml new file mode 100644 index 0000000..9631840 --- /dev/null +++ b/app/src/main/res/drawable/legend_item_high.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/legend_item_moderate.xml b/app/src/main/res/drawable/legend_item_moderate.xml new file mode 100644 index 0000000..898492e --- /dev/null +++ b/app/src/main/res/drawable/legend_item_moderate.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/legend_item_safe.xml b/app/src/main/res/drawable/legend_item_safe.xml new file mode 100644 index 0000000..7c1296b --- /dev/null +++ b/app/src/main/res/drawable/legend_item_safe.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml new file mode 100644 index 0000000..a28e5d8 --- /dev/null +++ b/app/src/main/res/drawable/rounded_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-night/substract_button_background.xml b/app/src/main/res/drawable/substract_button_background.xml similarity index 100% rename from app/src/main/res/drawable-night/substract_button_background.xml rename to app/src/main/res/drawable/substract_button_background.xml diff --git a/app/src/main/res/layout/activity_emoji_settings.xml b/app/src/main/res/layout/activity_emoji_settings.xml new file mode 100644 index 0000000..5190c66 --- /dev/null +++ b/app/src/main/res/layout/activity_emoji_settings.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1dae9aa..1ead3db 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -21,28 +21,35 @@ android:id="@+id/appTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="🌿 Contador CBD" - android:textSize="24sp" + android:text="@string/app_name_description" + android:textSize="20sp" android:textColor="@color/white" android:textStyle="bold" android:layout_marginTop="16dp" android:layout_marginStart="16dp" + android:layout_marginEnd="8dp" + android:maxLines="2" + android:ellipsize="end" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/themeSwitch" /> + app:layout_constraintEnd_toStartOf="@+id/themeSwitch" /> @@ -52,14 +59,29 @@ android:id="@+id/themeSwitch" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:layout_marginTop="16dp" android:showText="false" android:text="" android:contentDescription="Cambiar tema" app:useMaterialThemeColors="true" - app:layout_constraintTop_toTopOf="@id/appTitle" - app:layout_constraintBottom_toBottomOf="@id/appTitle" + app:layout_constraintTop_toTopOf="@+id/appTitle" + app:layout_constraintBottom_toBottomOf="@+id/appTitle" + app:layout_constraintEnd_toStartOf="@+id/settingsButton" /> + + + @@ -69,7 +91,7 @@ android:id="@+id/counterCard" android:layout_width="136dp" android:layout_height="128dp" - android:layout_marginTop="94dp" + android:layout_marginTop="115dp" app:cardCornerRadius="70dp" app:cardElevation="8dp" app:cardBackgroundColor="@color/surface" @@ -102,6 +124,19 @@ + + @@ -145,15 +180,38 @@ + + + @@ -205,7 +263,7 @@ android:paddingVertical="12dp" android:layout_marginTop="16dp" android:background="@color/stats_background" - app:layout_constraintTop_toBottomOf="@id/buttonContainer" + app:layout_constraintTop_toBottomOf="@+id/buttonContainer" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> @@ -248,7 +306,7 @@ android:background="@color/surface" app:tabMode="fixed" app:tabGravity="fill" - app:layout_constraintTop_toBottomOf="@id/statsContainer" + app:layout_constraintTop_toBottomOf="@+id/statsContainer" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> @@ -263,7 +321,7 @@ android:textColor="@color/text_primary" android:layout_marginStart="24dp" android:layout_marginTop="12dp" - app:layout_constraintTop_toBottomOf="@id/tabLayout" + app:layout_constraintTop_toBottomOf="@+id/tabLayout" app:layout_constraintStart_toStartOf="parent" /> @@ -277,7 +335,7 @@ android:scrollbars="vertical" android:scrollbarStyle="outsideOverlay" android:fadeScrollbars="false" - app:layout_constraintTop_toBottomOf="@id/historyTitle" + app:layout_constraintTop_toBottomOf="@+id/historyTitle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/activity_stats.xml b/app/src/main/res/layout/activity_stats.xml new file mode 100644 index 0000000..7150fb1 --- /dev/null +++ b/app/src/main/res/layout/activity_stats.xml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/cbd_widget.xml b/app/src/main/res/layout/cbd_widget.xml index a72127f..07b3812 100644 --- a/app/src/main/res/layout/cbd_widget.xml +++ b/app/src/main/res/layout/cbd_widget.xml @@ -80,7 +80,7 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_note_bottomsheet.xml b/app/src/main/res/layout/dialog_note_bottomsheet.xml index 538614b..415e74e 100644 --- a/app/src/main/res/layout/dialog_note_bottomsheet.xml +++ b/app/src/main/res/layout/dialog_note_bottomsheet.xml @@ -9,7 +9,7 @@ android:id="@+id/tvTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Nota del día" + android:text="@string/note_title" android:textStyle="bold" android:textSize="16sp" android:layout_marginBottom="8dp"/> @@ -22,7 +22,7 @@ android:id="@+id/etNote" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="Escribe tu nota…" + android:hint="@string/note_hint" android:minLines="3" android:gravity="top|start" /> @@ -38,14 +38,14 @@ android:id="@+id/btnDelete" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Borrar nota" + android:text="@string/note_delete" style="@style/Widget.Material3.Button.OutlinedButton"/> diff --git a/app/src/main/res/layout/item_calendar_day.xml b/app/src/main/res/layout/item_calendar_day.xml new file mode 100644 index 0000000..6bea490 --- /dev/null +++ b/app/src/main/res/layout/item_calendar_day.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_emoji_range.xml b/app/src/main/res/layout/item_emoji_range.xml new file mode 100644 index 0000000..9f24d7b --- /dev/null +++ b/app/src/main/res/layout/item_emoji_range.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 61ce5da..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 34e84a4..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 566138d..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b8f4d19..6c290cf 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -45,4 +45,17 @@ #6B46C1 #9F7AEA + + #1F5F3B + #2F855A + #48BB78 + #8038A169 + #C9F5D3 + #FF8C42 + #FDBA74 + #80FF8C42 + #33667EEA + #667eea + #6B7280 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 280b40c..a653f35 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,8 @@ + 🌿 Contador de CBD y THC CBD Counter - Contador rápido de CBD + Contador rápido de CBD y THC + 1 - 1 REINICIAR DÍA @@ -16,4 +17,30 @@ Contador reiniciado Exportar CSV Importar CSV + 📈 Stats + Calendario + Mes anterior + Mes siguiente + +1 + Añadir CBD aliñado + ¿Con qué está aliñado el CBD? + Etiqueta la toma con vibra 420. + weed + polen + CBD aliñado con weed agregado + CBD aliñado con polen agregado + (aliñado con %1$s) + Nota del día + Escribe tu nota… + Nota del día %1$s + Guardar + Borrar nota + CBD + + + ⚠️ Aviso Importante + CBDCounter es una herramienta de tracking personal.\n\n• Esta app NO es un dispositivo médico\n• NO proporciona consejo médico profesional\n• NO promueve el consumo de sustancias\n• NO facilita la compra ni venta de productos\n\nÚsala bajo tu propia responsabilidad y consulta siempre con un profesional de la salud para decisiones relacionadas con tu bienestar.\n\nAl continuar, aceptas que entiendes y estás de acuerdo con este aviso. + Entendido y acepto + No acepto + diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..6faccd5 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,28 @@ +# Configuración de GitHub Pages para CBDCounter + +# Tema (puedes cambiarlo luego si quieres) +theme: jekyll-theme-cayman + +# Información del sitio +title: CBDCounter +description: Tracking personal de CBD con total privacidad +lang: es-ES + +# URLs +url: "https://d4vram369.github.io" +baseurl: "/CBDcounter" + +# Autor +author: + name: D4vRAM + email: d4vram369@gmail.com + +# SEO +plugins: + - jekyll-seo-tag + +# Markdown +markdown: kramdown +kramdown: + input: GFM + syntax_highlighter: rouge diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..84b7ab4 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,86 @@ +# CBDCounter - Documentación Oficial + +
+ +🌿 **Tracking Personal de CBD con Total Privacidad** + +[![Versión](https://img.shields.io/badge/versión-1.1-green.svg)](https://github.com/tu-usuario/CBDcounter2/releases) +[![Android](https://img.shields.io/badge/Android-7.0%2B-blue.svg)](https://developer.android.com) +[![Licencia](https://img.shields.io/badge/licencia-GPL--3.0-orange.svg)](LICENSE) + +
+ +--- + +## 📱 Acerca de CBDCounter + +CBDCounter es una aplicación Android intuitiva y privada diseñada para el seguimiento personal de consumo de CBD. Con un enfoque en la privacidad absoluta, todos tus datos permanecen en tu dispositivo. + +**Características principales:** +- 📊 Contador diario con estadísticas detalladas +- 🏠 Widget de pantalla principal +- 📅 Calendario visual con emojis personalizables +- 📝 Sistema de notas con timestamps +- 💾 Exportación/Importación CSV +- 🔒 100% privado - cero recopilación de datos +- 🌙 Tema oscuro/claro + +--- + +## 📋 Documentación + +### Información Legal + +- **[Política de Privacidad](privacy-policy.md)** - Cómo manejamos (o mejor dicho, NO manejamos) tus datos + +### Soporte + +- **Email de contacto:** d4vram369@gmail.com +- **Tiempo de respuesta:** ~7 días hábiles + +--- + +## 🔒 Privacidad + +**Tu privacidad es nuestra prioridad #1:** + +✅ CERO recopilación de datos +✅ TODO se guarda localmente +✅ NO hay servidores externos +✅ NO usamos analytics +✅ NO compartimos nada con terceros + +--- + +## ⚠️ Descargo de Responsabilidad + +CBDCounter es una herramienta de tracking personal y **NO** constituye: +- Dispositivo médico +- Consejo médico profesional +- Promoción de consumo de sustancias +- Facilitación de compra/venta + +Consulta siempre con un profesional de la salud. + +--- + +## 🆓 Licencia + +Este proyecto está licenciado bajo GPL-3.0. Ver el archivo [LICENSE](../LICENSE) para más detalles. + +--- + +## 👨‍💻 Desarrollador + +**D4vRAM** +🇮🇨 Gran Canaria, España + +--- + +
+ +© 2025 D4vRAM. Todos los derechos reservados. + +Desarrollado con ❤️ desde Islas Canarias 🇮🇨 (España🇪 🇪🇸) + +
diff --git a/docs/privacy-policy.md b/docs/privacy-policy.md new file mode 100644 index 0000000..383e701 --- /dev/null +++ b/docs/privacy-policy.md @@ -0,0 +1,229 @@ +# Política de Privacidad de CBDCounter + +**Última actualización:** 10 de noviembre de 2025 + +**Desarrollador:** D4vRAM +**Contacto:** d4vram369@gmail.com + +--- + +## 1. Introducción + +CBDCounter ("la Aplicación") es una herramienta de seguimiento personal desarrollada por D4vRAM. Esta Política de Privacidad describe cómo manejamos la información en relación con el uso de la Aplicación. + +**En resumen:** Esta aplicación NO recopila, transmite, almacena en servidores ni comparte ningún dato personal. + +--- + +## 2. Información que Recopilamos + +**CBDCounter NO recopila ningún dato personal de los usuarios.** + +La Aplicación no: +- ❌ Recopila nombres, correos electrónicos o información de contacto +- ❌ Recopila datos de ubicación +- ❌ Accede a tu lista de contactos, fotos o archivos +- ❌ Rastrea tu actividad fuera de la app +- ❌ Utiliza cookies o tecnologías de seguimiento +- ❌ Recopila identificadores de dispositivo únicos +- ❌ Transmite datos a servidores externos + +--- + +## 3. Datos Almacenados Localmente + +Todos los datos que generes usando CBDCounter se almacenan **exclusivamente en tu dispositivo** mediante la tecnología SharedPreferences de Android: + +### Datos almacenados localmente: +- **Contadores diarios:** Número de consumos registrados por fecha +- **Notas personales:** Texto que escribas para cada día +- **Emojis personalizados:** Configuraciones visuales que hayas modificado +- **Preferencias de tema:** Modo oscuro/claro +- **Estado del disclaimer:** Indicador de que aceptaste el aviso legal + +### Características importantes: +✅ Estos datos **NUNCA** salen de tu dispositivo +✅ NO se sincronizan con ningún servidor +✅ NO son accesibles por el desarrollador +✅ Son eliminados automáticamente si desinstalas la app + +--- + +## 4. Función de Exportación CSV + +La Aplicación permite exportar tus datos a un archivo CSV para crear copias de seguridad personales. + +**Importante:** +- Tú controlas este archivo exportado +- El archivo se guarda en tu dispositivo +- Puedes compartirlo manualmente mediante las opciones estándar de Android +- El desarrollador NO tiene acceso a este archivo +- Tú eres responsable de cómo compartes o almacenas este archivo + +--- + +## 5. Permisos de Android + +La Aplicación solicita los siguientes permisos: + +### `RECEIVE_BOOT_COMPLETED` +**Propósito:** Restaurar el widget de pantalla principal después de reiniciar el dispositivo. +**Uso:** Solo se utiliza para actualizar el widget, no para rastrear ni monitorear actividad. + +**Ningún otro permiso es solicitado o utilizado.** + +--- + +## 6. Servicios de Terceros + +CBDCounter **NO utiliza**: +- ❌ Servicios de analytics (Google Analytics, Firebase, etc.) +- ❌ Redes publicitarias +- ❌ Sistemas de tracking o métricas +- ❌ SDKs de terceros (excepto AndroidX y Material Design de Google, que son librerías estándar de UI sin capacidad de recopilación de datos) + +--- + +## 7. Seguridad de los Datos + +Dado que todos los datos se almacenan localmente en tu dispositivo: + +- La seguridad depende de la protección de tu dispositivo (PIN, huella, etc.) +- En dispositivos rooteados, aplicaciones con permisos de root podrían acceder a los datos +- Recomendamos usar las funciones de seguridad nativas de Android + +**Nota:** Si tu dispositivo es compartido con otras personas, ellas podrían acceder a la Aplicación y ver tus datos. + +--- + +## 8. Privacidad de Menores + +CBDCounter no está dirigida a menores de 13 años. Dado que no recopilamos ningún dato, no recopilamos intencionalmente información de niños menores de 13 años. + +--- + +## 9. Cambios a esta Política + +Podemos actualizar esta Política de Privacidad ocasionalmente. Te notificaremos cualquier cambio publicando la nueva Política en esta página y actualizando la fecha de "Última actualización". + +**Es tu responsabilidad revisar esta Política periódicamente.** El uso continuado de la Aplicación después de cambios constituye aceptación de dichos cambios. + +--- + +## 10. Limitaciones y Descargo de Responsabilidad + +### CBDCounter NO es un dispositivo médico + +- Esta Aplicación NO proporciona consejo médico, diagnóstico o tratamiento +- NO está destinada a sustituir la consulta con un profesional de la salud +- El contenido es solo para fines informativos y de seguimiento personal +- Siempre consulta con un médico u otro profesional de la salud calificado sobre cualquier pregunta relacionada con tu salud + +### Uso de la Aplicación + +- El uso de CBDCounter es bajo tu propio riesgo +- La Aplicación se proporciona "tal cual" sin garantías de ningún tipo +- El desarrollador no se hace responsable del uso que hagas de los datos rastreados +- No promovemos, facilitamos ni apoyamos actividades ilegales + +### Legalidad del CBD + +- Es responsabilidad del usuario verificar la legalidad del CBD en su jurisdicción +- El desarrollador no asume responsabilidad por el uso de la Aplicación en jurisdicciones donde el CBD es ilegal +- Esta Aplicación NO facilita la compra, venta ni distribución de CBD u otras sustancias + +--- + +## 11. Tus Derechos + +Dado que no recopilamos ni almacenamos datos fuera de tu dispositivo: + +### Derecho de acceso +✅ Todos tus datos están siempre accesibles en la Aplicación + +### Derecho de rectificación +✅ Puedes editar tus notas y datos en cualquier momento + +### Derecho de eliminación +✅ Simplemente desinstala la Aplicación o usa la función de "reset" en ajustes + +### Derecho de portabilidad +✅ Usa la función "Exportar CSV" para obtener tus datos en formato portable + +### Derecho de oposición +✅ No aplica, ya que no procesamos tus datos + +--- + +## 12. Cumplimiento Legal + +Esta Política de Privacidad cumple con: +- **RGPD** (Reglamento General de Protección de Datos de la UE) +- **LOPD** (Ley Orgánica de Protección de Datos de España) +- **Políticas de Privacidad de Google Play** + +--- + +## 13. Transferencias Internacionales + +No aplicable. Tus datos nunca abandonan tu dispositivo. + +--- + +## 14. Retención de Datos + +Los datos se retienen mientras: +- Mantengas la Aplicación instalada en tu dispositivo +- No uses la función de reset o borrado manual + +Los datos se eliminan automáticamente cuando: +- Desinstalas la Aplicación +- Borras manualmente los datos desde configuración de Android +- Reseteas tu dispositivo a valores de fábrica + +--- + +## 15. Base Legal para el Procesamiento (RGPD) + +No aplicable, ya que no procesamos datos personales fuera de tu dispositivo. + +El almacenamiento local en tu dispositivo está bajo tu control exclusivo. + +--- + +## 16. Contacto + +Si tienes preguntas, comentarios o inquietudes sobre esta Política de Privacidad: + +**Email:** d4vram369@gmail.com +**Método de contacto preferido:** Email +**Tiempo de respuesta esperado:** 7 días hábiles + +--- + +## 17. Jurisdicción + +Esta Política de Privacidad se rige por las leyes de España. + +--- + +## 18. Idioma + +Esta Política está disponible en español. En caso de discrepancia entre traducciones, la versión en español prevalecerá. + +--- + +## Resumen Ejecutivo (TL;DR) + +✅ **NO recopilamos datos** +✅ **Todo se guarda en tu dispositivo** +✅ **NO compartimos nada con nadie** +✅ **NO usamos analytics ni publicidad** +✅ **Tú controlas 100% de tus datos** +✅ **Desinstalar = eliminar todo** + +--- + +**Fecha de entrada en vigor:** 10 de noviembre de 2025 + +© 2025 D4vRAM. Todos los derechos reservados. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8502a6..f23a832 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.3" +agp = "8.13.0" kotlin = "2.0.21" coreKtx = "1.10.1" junit = "4.13.2" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..9bbc975 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ