Comparar commits
3 Commits
| Autor | SHA1 | Fecha | |
|---|---|---|---|
|
15f4d2eead
|
|||
|
95e8d167f2
|
|||
|
e7606df296
|
347
README.md
347
README.md
@@ -1,207 +1,210 @@
|
|||||||
# Motívame - Tu Compañero de Motivación Diaria
|
# Motívame · Tu Compañero de Motivación Diaria
|
||||||
|
|
||||||
## 📱 Descripción
|
<p align="center">
|
||||||
|
<img src="app/src/main/ic_launcher-playstore.png" width="120" alt="Motívame icon"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
**Motívame** es una aplicación Android moderna diseñada para ayudarte a mantener la motivación en tus tareas pendientes. La app te permite configurar recordatorios diarios personalizados con tus metas específicas, ayudándote a visualizar el "por qué" detrás de cada tarea.
|
<p align="center">
|
||||||
|
<a href="https://github.com/manalejandro/motivame/releases"><img src="https://img.shields.io/github/v/release/manalejandro/motivame?color=6366F1&label=versión" alt="Release"/></a>
|
||||||
|
<img src="https://img.shields.io/badge/Android-7.0%2B-brightgreen?logo=android" alt="Android 7+"/>
|
||||||
|
<img src="https://img.shields.io/badge/Kotlin-2.0.21-blue?logo=kotlin" alt="Kotlin"/>
|
||||||
|
<img src="https://img.shields.io/badge/Jetpack%20Compose-2024.09-orange" alt="Compose"/>
|
||||||
|
<img src="https://img.shields.io/badge/licencia-MIT-lightgrey" alt="MIT"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
## ✨ Características Principales
|
> **Motívame** es una app Android de código abierto que te ayuda a mantener la motivación en tus hábitos y tareas pendientes. Define tus metas, elige con qué frecuencia quieres que te recuerde y deja que la app haga el resto.
|
||||||
|
|
||||||
- **📝 Gestión de Tareas**: Crea, edita y elimina tareas pendientes fácilmente
|
---
|
||||||
- **🎯 Definición de Metas**: Asocia múltiples objetivos a cada tarea para recordar por qué es importante
|
|
||||||
- **🔔 Notificaciones Diarias**: Recibe recordatorios automáticos todos los días a las 9:00 AM
|
|
||||||
- **🔊 Alertas Personalizables**: Configura sonido y vibración según tus preferencias
|
|
||||||
- **⏯️ Control de Tareas**: Activa o pausa tareas según tu conveniencia
|
|
||||||
- **🎨 Diseño Moderno**: Interfaz Material Design 3 con colores vibrantes y motivadores
|
|
||||||
- **📊 Tareas Predeterminadas**: Comienza con ejemplos inspiradores o crea las tuyas propias
|
|
||||||
|
|
||||||
## 🚀 Funcionalidades Técnicas
|
## 📥 Descarga
|
||||||
|
|
||||||
### Arquitectura
|
👉 [github.com/manalejandro/motivame](https://github.com/manalejandro/motivame)
|
||||||
- **MVVM (Model-View-ViewModel)**: Separación clara de responsabilidades
|
|
||||||
- **Jetpack Compose**: UI moderna y declarativa
|
|
||||||
- **WorkManager**: Tareas programadas en segundo plano confiables
|
|
||||||
- **DataStore**: Persistencia de datos ligera y eficiente
|
|
||||||
- **Kotlin Coroutines**: Programación asíncrona fluida
|
|
||||||
|
|
||||||
### Componentes Principales
|
---
|
||||||
|
|
||||||
#### 1. Pantalla Principal
|
## ✨ Características
|
||||||
- Lista de tareas activas y pausadas
|
|
||||||
- Tarjetas visuales con gradientes
|
|
||||||
- Indicadores de estado (activo/pausado)
|
|
||||||
- Navegación rápida a configuración y agregar tareas
|
|
||||||
|
|
||||||
#### 2. Agregar Tareas
|
| Función | Descripción |
|
||||||
- Campo de título de tarea
|
|---|---|
|
||||||
- Agregar múltiples metas personalizadas
|
| 📝 **Gestión de tareas** | Crea, edita (pulsación larga) y elimina tareas |
|
||||||
- Validación de campos
|
| 🎯 **Metas por tarea** | Asocia múltiples objetivos a cada tarea |
|
||||||
- Interfaz intuitiva con iconos descriptivos
|
| ⏯️ **Pausa / Reanudar** | Desactiva temporalmente una tarea sin borrarla |
|
||||||
|
| 🔔 **Avisos personalizables** | Elige cuántos avisos al día (1–10) y cada cuántos días se repite el ciclo |
|
||||||
|
| 🎲 **Horarios aleatorios** | Cada aviso se programa a una hora distinta dentro de la franja 9:00–21:00 |
|
||||||
|
| 🔊 **Sonido configurable** | Activa o desactiva el sonido de las notificaciones |
|
||||||
|
| 🌐 **Multiidioma** | 8 idiomas: Español · English · 中文 · Français · Deutsch · Português · 日本語 · 한국어 |
|
||||||
|
| 🎨 **Material Design 3** | Interfaz moderna con gradientes, colores vibrantes y soporte edge-to-edge |
|
||||||
|
| 🟣 **Widget** | Widget de escritorio que muestra la tarea activa y una meta aleatoria |
|
||||||
|
|
||||||
#### 3. Configuración
|
---
|
||||||
- Activar/desactivar notificaciones
|
|
||||||
- Control de sonido
|
|
||||||
- Probar notificaciones en tiempo real
|
|
||||||
- Solicitud de permisos en Android 13+
|
|
||||||
|
|
||||||
### Sistema de Notificaciones
|
## 📱 Capturas de pantalla
|
||||||
|
|
||||||
La aplicación utiliza un sistema de notificaciones inteligente:
|
| Principal | Añadir tarea | Configuración |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| *(lista de tareas con resumen de avisos)* | *(formulario con metas y frecuencia)* | *(idioma, notificaciones, sonido)* |
|
||||||
|
|
||||||
- **Canal de Alta Prioridad**: Garantiza que las notificaciones sean visibles
|
---
|
||||||
- **Vibración Personalizada**: Patrón distintivo para llamar la atención
|
|
||||||
- **Mensajes Motivacionales**: Cada notificación muestra una meta aleatoria de la tarea
|
|
||||||
- **Sonido Configurable**: Opción de activar/desactivar sonido de notificación
|
|
||||||
|
|
||||||
### WorkManager - Recordatorios Diarios
|
## 🚀 Cómo funciona
|
||||||
|
|
||||||
- Ejecuta tareas diarias a las 9:00 AM
|
1. **Crea una tarea** — ponle título y añade tus metas (el «por qué»).
|
||||||
- Persiste incluso después de reiniciar el dispositivo
|
2. **Configura la frecuencia** — número de avisos diarios y cada cuántos días se repite el ciclo.
|
||||||
- Optimizado para el consumo de batería
|
3. **Recibe recordatorios** — la app programa los avisos a horas aleatorias distintas dentro de 9:00–21:00, distribuidos en días diferentes del ciclo para que no todos lleguen el mismo día.
|
||||||
- No requiere conexión a Internet
|
4. **Pausa o edita** — mantén pulsada una tarea para editarla o usa el botón ⏸ para pausarla sin perder su configuración.
|
||||||
|
|
||||||
## 📦 Dependencias
|
---
|
||||||
|
|
||||||
```kotlin
|
## 🏗️ Arquitectura y tecnología
|
||||||
// Core Android
|
|
||||||
androidx.core:core-ktx:1.10.1
|
|
||||||
androidx.lifecycle:lifecycle-runtime-ktx:2.6.1
|
|
||||||
androidx.activity:activity-compose:1.8.0
|
|
||||||
|
|
||||||
// Compose
|
```
|
||||||
androidx.compose:compose-bom:2024.09.00
|
MVVM · Jetpack Compose · WorkManager · DataStore · Kotlin Coroutines
|
||||||
androidx.compose.material3:material3
|
|
||||||
androidx.compose.material:material-icons-extended:1.5.4
|
|
||||||
|
|
||||||
// Architecture Components
|
|
||||||
androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1
|
|
||||||
androidx.work:work-runtime-ktx:2.9.0
|
|
||||||
androidx.datastore:datastore-preferences:1.0.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Requisitos
|
### Estructura del proyecto
|
||||||
|
|
||||||
- **Android SDK 24+** (Android 7.0 Nougat o superior)
|
|
||||||
- **Target SDK 36**
|
|
||||||
- **Kotlin 2.0.21**
|
|
||||||
- **Gradle 9.0.1**
|
|
||||||
|
|
||||||
## 🎨 Diseño
|
|
||||||
|
|
||||||
### Paleta de Colores
|
|
||||||
|
|
||||||
- **Primary**: Indigo vibrante (#6366F1)
|
|
||||||
- **Secondary**: Rosa motivador (#EC4899)
|
|
||||||
- **Tertiary**: Púrpura (#8B5CF6)
|
|
||||||
- **Success**: Verde (#10B981)
|
|
||||||
- **Error**: Rojo (#EF4444)
|
|
||||||
|
|
||||||
### Tipografía
|
|
||||||
- Fuentes Material Design 3
|
|
||||||
- Énfasis en títulos grandes y legibles
|
|
||||||
- Texto secundario con contraste óptimo
|
|
||||||
|
|
||||||
## 📱 Permisos
|
|
||||||
|
|
||||||
La aplicación solicita los siguientes permisos:
|
|
||||||
|
|
||||||
- `POST_NOTIFICATIONS` (Android 13+): Para mostrar recordatorios
|
|
||||||
- `VIBRATE`: Para alertas con vibración
|
|
||||||
- `RECEIVE_BOOT_COMPLETED`: Para mantener recordatorios después de reiniciar
|
|
||||||
|
|
||||||
## 🔄 Flujo de la Aplicación
|
|
||||||
|
|
||||||
1. **Inicio**: Pantalla principal con tareas predeterminadas
|
|
||||||
2. **Agregar Tarea**: El usuario crea una nueva tarea con sus metas
|
|
||||||
3. **Configuración**: Personaliza notificaciones y sonido
|
|
||||||
4. **Recordatorios Automáticos**: WorkManager envía notificaciones diarias
|
|
||||||
5. **Interacción**: Usuario puede pausar, reanudar o eliminar tareas
|
|
||||||
|
|
||||||
## 🏗️ Estructura del Proyecto
|
|
||||||
|
|
||||||
```
|
```
|
||||||
app/src/main/java/com/manalejandro/motivame/
|
app/src/main/java/com/manalejandro/motivame/
|
||||||
├── data/
|
├── data/
|
||||||
│ ├── Task.kt # Modelo de datos
|
│ ├── Task.kt # Modelo de datos
|
||||||
│ └── TaskRepository.kt # Repositorio de persistencia
|
│ └── TaskRepository.kt # Persistencia con DataStore
|
||||||
├── notifications/
|
├── notifications/
|
||||||
│ └── NotificationHelper.kt # Gestión de notificaciones
|
│ └── NotificationHelper.kt # Envío de notificaciones (Ringtone independiente del canal)
|
||||||
├── ui/
|
├── ui/
|
||||||
│ ├── screens/
|
│ ├── screens/
|
||||||
│ │ ├── MainScreen.kt # Pantalla principal
|
│ │ ├── MainScreen.kt # Lista de tareas
|
||||||
│ │ ├── AddTaskScreen.kt # Pantalla agregar tarea
|
│ │ ├── AddTaskScreen.kt # Crear / editar tarea
|
||||||
│ │ └── SettingsScreen.kt # Pantalla configuración
|
│ │ └── SettingsScreen.kt # Configuración (idioma, notificaciones, sonido)
|
||||||
│ ├── theme/
|
│ ├── theme/
|
||||||
│ │ ├── Color.kt # Definición de colores
|
│ │ ├── Color.kt
|
||||||
│ │ ├── Theme.kt # Tema de la aplicación
|
│ │ ├── Theme.kt
|
||||||
│ │ └── Type.kt # Tipografía
|
│ │ └── Type.kt
|
||||||
│ └── viewmodel/
|
│ └── viewmodel/
|
||||||
│ └── TaskViewModel.kt # ViewModel principal
|
│ └── TaskViewModel.kt # Estado y lógica de negocio
|
||||||
|
├── util/
|
||||||
|
│ └── LocaleHelper.kt # Cambio de idioma en tiempo de ejecución
|
||||||
├── worker/
|
├── worker/
|
||||||
│ └── DailyReminderWorker.kt # Worker para recordatorios
|
│ └── DailyReminderWorker.kt # WorkManager: ejecuta recordatorios programados
|
||||||
└── MainActivity.kt # Actividad principal
|
├── MotivameApplication.kt # Application: inicializa el canal de notificación
|
||||||
|
└── MainActivity.kt # Actividad principal + navegación Compose
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Compilación
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Compilar versión de depuración
|
|
||||||
./gradlew assembleDebug
|
|
||||||
|
|
||||||
# Compilar versión de lanzamiento
|
|
||||||
./gradlew assembleRelease
|
|
||||||
|
|
||||||
# Ejecutar tests
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
# Compilar e instalar
|
|
||||||
./gradlew installDebug
|
|
||||||
```
|
|
||||||
|
|
||||||
## 💡 Casos de Uso
|
|
||||||
|
|
||||||
1. **Estudiante**: Recordatorios para estudiar materias específicas con metas académicas
|
|
||||||
2. **Fitness**: Mantener rutina de ejercicio con objetivos de salud
|
|
||||||
3. **Desarrollo Personal**: Hábitos diarios como lectura, meditación, etc.
|
|
||||||
4. **Productividad**: Tareas profesionales con objetivos de carrera
|
|
||||||
|
|
||||||
## 📝 Tareas Predeterminadas
|
|
||||||
|
|
||||||
La app incluye 3 tareas de ejemplo:
|
|
||||||
|
|
||||||
1. **Hacer ejercicio**
|
|
||||||
- Mejorar salud cardiovascular
|
|
||||||
- Sentirse más energético
|
|
||||||
- Alcanzar peso ideal
|
|
||||||
|
|
||||||
2. **Estudiar inglés**
|
|
||||||
- Mejores oportunidades laborales
|
|
||||||
- Viajar sin limitaciones
|
|
||||||
- Expandir conocimiento
|
|
||||||
|
|
||||||
3. **Leer 30 minutos**
|
|
||||||
- Desarrollar hábito de lectura
|
|
||||||
- Aprender cosas nuevas
|
|
||||||
- Reducir tiempo en redes sociales
|
|
||||||
|
|
||||||
## 🎯 Roadmap Futuro
|
|
||||||
|
|
||||||
- [ ] Estadísticas de cumplimiento
|
|
||||||
- [ ] Múltiples recordatorios por día
|
|
||||||
- [ ] Widgets de pantalla de inicio
|
|
||||||
- [ ] Compartir progreso
|
|
||||||
- [ ] Temas personalizables
|
|
||||||
- [ ] Backup en la nube
|
|
||||||
- [ ] Recordatorios inteligentes basados en ubicación
|
|
||||||
|
|
||||||
## 👨💻 Autor
|
|
||||||
|
|
||||||
Desarrollado con ❤️ para ayudar a las personas a mantener su motivación
|
|
||||||
|
|
||||||
## 📄 Licencia
|
|
||||||
|
|
||||||
Este proyecto es de código abierto y está disponible bajo la licencia MIT.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**¡Mantente motivado y alcanza tus metas! 🚀**
|
## 🌐 Idiomas soportados
|
||||||
|
|
||||||
|
| Código | Idioma |
|
||||||
|
|---|---|
|
||||||
|
| `es` | 🇪🇸 Español *(predeterminado)* |
|
||||||
|
| `en` | 🇬🇧 English |
|
||||||
|
| `zh` | 🇨🇳 中文 |
|
||||||
|
| `fr` | 🇫🇷 Français |
|
||||||
|
| `de` | 🇩🇪 Deutsch |
|
||||||
|
| `pt` | 🇵🇹 Português |
|
||||||
|
| `ja` | 🇯🇵 日本語 |
|
||||||
|
| `ko` | 🇰🇷 한국어 |
|
||||||
|
|
||||||
|
El idioma se selecciona desde **Configuración → Idioma** y se aplica instantáneamente sin necesidad de reiniciar el dispositivo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔔 Sistema de notificaciones
|
||||||
|
|
||||||
|
- **Franja horaria**: 9:00–21:00
|
||||||
|
- **Horas aleatorias únicas**: cada aviso del ciclo tiene una hora distinta a las demás
|
||||||
|
- **Distribución en días**: los avisos se reparten entre los días del ciclo para no coincidir todos el mismo día
|
||||||
|
- **Sonido independiente del canal**: el sonido se reproduce con `RingtoneManager` directamente, sin depender del estado interno del canal de Android — garantiza comportamiento consistente en todos los dispositivos y versiones
|
||||||
|
- **Canal único con `setSilent(true)`**: la notificación visual se envía siempre silenciosa a nivel de canal; el sonido se controla únicamente desde la preferencia del usuario
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Dependencias principales
|
||||||
|
|
||||||
|
| Librería | Versión |
|
||||||
|
|---|---|
|
||||||
|
| Kotlin | 2.0.21 |
|
||||||
|
| Jetpack Compose BOM | 2024.09.00 |
|
||||||
|
| Activity Compose | 1.8.0 |
|
||||||
|
| Lifecycle / ViewModel | 2.6.1 |
|
||||||
|
| WorkManager | 2.9.0 |
|
||||||
|
| DataStore Preferences | 1.0.0 |
|
||||||
|
| Material Icons Extended | 1.5.4 |
|
||||||
|
| Core KTX | 1.10.1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Requisitos
|
||||||
|
|
||||||
|
- **Android 7.0+** (API 24)
|
||||||
|
- **Target SDK**: 36
|
||||||
|
- **Gradle**: 9.0.1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Permisos
|
||||||
|
|
||||||
|
| Permiso | Motivo |
|
||||||
|
|---|---|
|
||||||
|
| `POST_NOTIFICATIONS` *(Android 13+)* | Mostrar recordatorios |
|
||||||
|
| `VIBRATE` | Vibración en las notificaciones |
|
||||||
|
| `RECEIVE_BOOT_COMPLETED` | Reprogramar avisos tras reinicio del dispositivo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Compilación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debug
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Release
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
# Instalar en dispositivo conectado
|
||||||
|
./gradlew installDebug
|
||||||
|
|
||||||
|
# Tests unitarios
|
||||||
|
./gradlew test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Casos de uso
|
||||||
|
|
||||||
|
- **Estudiante** — Recordatorios de estudio con metas académicas concretas
|
||||||
|
- **Fitness** — Mantener rutina de ejercicio con objetivos de salud
|
||||||
|
- **Desarrollo personal** — Lectura, meditación, idiomas…
|
||||||
|
- **Productividad profesional** — Tareas con objetivos de carrera
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
- [x] Gestión de tareas (crear, editar, eliminar, pausar)
|
||||||
|
- [x] Múltiples avisos por día con horas aleatorias
|
||||||
|
- [x] Ciclo de días configurable
|
||||||
|
- [x] Multiidioma (8 idiomas)
|
||||||
|
- [x] Sonido configurable independiente del canal Android
|
||||||
|
- [x] Widget de pantalla de inicio
|
||||||
|
- [ ] Estadísticas de cumplimiento
|
||||||
|
- [ ] Widget de pantalla de inicio
|
||||||
|
- [ ] Backup en la nube
|
||||||
|
- [ ] Temas personalizables (claro / oscuro / AMOLED)
|
||||||
|
- [ ] Recordatorios con imagen motivacional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👨💻 Autor
|
||||||
|
|
||||||
|
Desarrollado por **[manalejandro.com](https://manalejandro.com)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Licencia
|
||||||
|
|
||||||
|
Este proyecto está disponible bajo la licencia **MIT**.
|
||||||
|
Puedes usarlo, modificarlo y distribuirlo libremente citando al autor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center"><strong>¡Mantente motivado y alcanza tus metas! 🚀</strong></p>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:name=".MotivameApplication"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -26,6 +27,21 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<receiver
|
||||||
|
android:name=".widget.MotivameWidget"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
<action android:name="com.manalejandro.motivame.WIDGET_REFRESH" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/motivame_widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,86 +1,213 @@
|
|||||||
package com.manalejandro.motivame
|
package com.manalejandro.motivame
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
|
import com.manalejandro.motivame.data.Task
|
||||||
|
import com.manalejandro.motivame.data.TaskRepository
|
||||||
import com.manalejandro.motivame.ui.screens.AddTaskScreen
|
import com.manalejandro.motivame.ui.screens.AddTaskScreen
|
||||||
import com.manalejandro.motivame.ui.screens.MainScreen
|
import com.manalejandro.motivame.ui.screens.MainScreen
|
||||||
import com.manalejandro.motivame.ui.screens.SettingsScreen
|
import com.manalejandro.motivame.ui.screens.SettingsScreen
|
||||||
import com.manalejandro.motivame.ui.theme.MotivameTheme
|
import com.manalejandro.motivame.ui.theme.MotivameTheme
|
||||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||||
|
import com.manalejandro.motivame.util.LocaleHelper
|
||||||
import com.manalejandro.motivame.worker.DailyReminderWorker
|
import com.manalejandro.motivame.worker.DailyReminderWorker
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.Calendar
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(newBase: Context) {
|
||||||
|
// Leer idioma de forma síncrona antes de inflar la UI
|
||||||
|
val langCode = runCatching {
|
||||||
|
kotlinx.coroutines.runBlocking {
|
||||||
|
TaskRepository(newBase).language.first()
|
||||||
|
}
|
||||||
|
}.getOrDefault("es")
|
||||||
|
super.attachBaseContext(LocaleHelper.wrap(newBase, langCode))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
// Configurar recordatorios diarios
|
// Programar recordatorios para todas las tareas activas
|
||||||
setupDailyReminders()
|
scheduleAllReminders()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
MotivameTheme {
|
MotivameTheme {
|
||||||
MotivameApp()
|
MotivameApp(onRescheduleReminders = { enabled -> scheduleAllReminders(enabled) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupDailyReminders() {
|
/**
|
||||||
val constraints = Constraints.Builder()
|
* Cancela todos los workers anteriores y programa nuevos recordatorios
|
||||||
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
|
* para cada tarea activa, distribuyendo los avisos entre las 9:00 y las 21:00.
|
||||||
.build()
|
* @param notificationsEnabled valor ya conocido, para evitar condición de carrera con DataStore.
|
||||||
|
* Si es null, se lee del DataStore (solo al arrancar la app).
|
||||||
|
*/
|
||||||
|
fun scheduleAllReminders(notificationsEnabled: Boolean? = null) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val repository = TaskRepository(applicationContext)
|
||||||
|
|
||||||
val dailyWorkRequest = PeriodicWorkRequestBuilder<DailyReminderWorker>(
|
// Cancelar todos los workers existentes de recordatorios de tareas
|
||||||
1, TimeUnit.DAYS
|
WorkManager.getInstance(applicationContext)
|
||||||
)
|
.cancelAllWorkByTag("task_reminder")
|
||||||
.setConstraints(constraints)
|
|
||||||
.setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork(
|
val enabled = notificationsEnabled ?: repository.notificationEnabled.first()
|
||||||
"daily_reminder",
|
if (!enabled) return@launch
|
||||||
ExistingPeriodicWorkPolicy.KEEP,
|
|
||||||
dailyWorkRequest
|
val tasks = repository.tasks.first()
|
||||||
)
|
tasks.filter { it.isActive }.forEach { task ->
|
||||||
|
scheduleRemindersForTask(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateInitialDelay(): Long {
|
private fun scheduleRemindersForTask(task: Task) {
|
||||||
val currentTime = System.currentTimeMillis()
|
val reminders = task.dailyReminders.coerceIn(1, 10)
|
||||||
val calendar = java.util.Calendar.getInstance().apply {
|
val cycleDays = task.repeatEveryDays.coerceIn(1, 30)
|
||||||
timeInMillis = currentTime
|
val workManager = WorkManager.getInstance(applicationContext)
|
||||||
set(java.util.Calendar.HOUR_OF_DAY, 9) // 9 AM
|
|
||||||
set(java.util.Calendar.MINUTE, 0)
|
// Ventana de notificaciones: 9:00 a 21:00 (720 minutos disponibles)
|
||||||
set(java.util.Calendar.SECOND, 0)
|
val windowStartMinute = 9 * 60 // 540
|
||||||
|
val windowEndMinute = 21 * 60 // 1260
|
||||||
|
val windowSize = windowEndMinute - windowStartMinute // 720
|
||||||
|
|
||||||
|
// Distribuir los N avisos en días distintos dentro del ciclo.
|
||||||
|
// Si reminders <= cycleDays cada aviso va a un día diferente;
|
||||||
|
// si hay más avisos que días, se reparten de forma ciclica.
|
||||||
|
val dayAssignments = (0 until reminders).map { i -> i % cycleDays }
|
||||||
|
|
||||||
|
// Generar horas aleatorias únicas (en minutos desde medianoche)
|
||||||
|
// Para cada aviso elegimos un minuto al azar dentro de [540, 1260)
|
||||||
|
// asegurándonos de que no coincida con ningún otro aviso ya asignado.
|
||||||
|
val usedMinutes = mutableSetOf<Int>()
|
||||||
|
val minuteAssignments = mutableListOf<Int>()
|
||||||
|
|
||||||
|
repeat(reminders) {
|
||||||
|
var candidate: Int
|
||||||
|
var attempts = 0
|
||||||
|
do {
|
||||||
|
candidate = windowStartMinute + Random.nextInt(windowSize)
|
||||||
|
attempts++
|
||||||
|
// Tras muchos intentos (espacio muy saturado) relajamos la condición
|
||||||
|
// exigiendo sólo minutos distintos en el mismo día
|
||||||
|
} while (usedMinutes.contains(candidate) && attempts < windowSize)
|
||||||
|
usedMinutes.add(candidate)
|
||||||
|
minuteAssignments.add(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (calendar.timeInMillis <= currentTime) {
|
for (i in 0 until reminders) {
|
||||||
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
|
val dayOffset = dayAssignments[i]
|
||||||
|
val totalMinutes = minuteAssignments[i]
|
||||||
|
val targetHour = totalMinutes / 60
|
||||||
|
val targetMinute = totalMinutes % 60
|
||||||
|
|
||||||
|
val delayMs = calculateDelayToTimeWithDayOffset(targetHour, targetMinute, dayOffset)
|
||||||
|
|
||||||
|
val inputData = workDataOf(DailyReminderWorker.KEY_TASK_ID to task.id)
|
||||||
|
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<DailyReminderWorker>()
|
||||||
|
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(inputData)
|
||||||
|
.addTag("task_reminder")
|
||||||
|
.addTag("task_${task.id}")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
workManager.enqueue(workRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcula el retardo hasta la hora indicada más un desplazamiento de días.
|
||||||
|
* Si la hora ya pasó hoy, se mueve al día siguiente antes de aplicar el offset.
|
||||||
|
*/
|
||||||
|
private fun calculateDelayToTimeWithDayOffset(hour: Int, minute: Int, dayOffset: Int): Long {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val calendar = Calendar.getInstance().apply {
|
||||||
|
timeInMillis = now
|
||||||
|
set(Calendar.HOUR_OF_DAY, hour)
|
||||||
|
set(Calendar.MINUTE, minute)
|
||||||
|
set(Calendar.SECOND, 0)
|
||||||
|
set(Calendar.MILLISECOND, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return calendar.timeInMillis - currentTime
|
// Si ya pasó esa hora hoy, mover a mañana antes de aplicar el offset
|
||||||
|
if (calendar.timeInMillis <= now) {
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar el offset de días adicionales
|
||||||
|
if (dayOffset > 0) {
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, dayOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return calendar.timeInMillis - now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MotivameApp() {
|
fun MotivameApp(onRescheduleReminders: (Boolean) -> Unit = {}) {
|
||||||
val viewModel: TaskViewModel = viewModel()
|
val viewModel: TaskViewModel = viewModel()
|
||||||
|
val context = LocalContext.current
|
||||||
var currentScreen by remember { mutableStateOf("main") }
|
var currentScreen by remember { mutableStateOf("main") }
|
||||||
|
var taskToEdit by remember { mutableStateOf<Task?>(null) }
|
||||||
|
|
||||||
|
// Registrar callback para reprogramar avisos cuando cambian las tareas
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.onRescheduleReminders = { enabled -> onRescheduleReminders(enabled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptar el botón físico Atrás del sistema
|
||||||
|
BackHandler(enabled = currentScreen != "main") {
|
||||||
|
taskToEdit = null
|
||||||
|
currentScreen = "main"
|
||||||
|
}
|
||||||
|
// En la pantalla principal, minimizar en lugar de cerrar
|
||||||
|
BackHandler(enabled = currentScreen == "main") {
|
||||||
|
(context as? ComponentActivity)?.moveTaskToBack(true)
|
||||||
|
}
|
||||||
|
|
||||||
when (currentScreen) {
|
when (currentScreen) {
|
||||||
"main" -> MainScreen(
|
"main" -> MainScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateToAddTask = { currentScreen = "add_task" },
|
onNavigateToAddTask = {
|
||||||
onNavigateToSettings = { currentScreen = "settings" }
|
taskToEdit = null
|
||||||
|
currentScreen = "add_task"
|
||||||
|
},
|
||||||
|
onNavigateToSettings = { currentScreen = "settings" },
|
||||||
|
onEditTask = { task ->
|
||||||
|
taskToEdit = task
|
||||||
|
currentScreen = "edit_task"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
"add_task" -> AddTaskScreen(
|
"add_task" -> AddTaskScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateBack = { currentScreen = "main" }
|
onNavigateBack = { currentScreen = "main" }
|
||||||
)
|
)
|
||||||
|
"edit_task" -> AddTaskScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onNavigateBack = {
|
||||||
|
taskToEdit = null
|
||||||
|
currentScreen = "main"
|
||||||
|
},
|
||||||
|
taskToEdit = taskToEdit
|
||||||
|
)
|
||||||
"settings" -> SettingsScreen(
|
"settings" -> SettingsScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateBack = { currentScreen = "main" }
|
onNavigateBack = { currentScreen = "main" }
|
||||||
|
|||||||
45
app/src/main/java/com/manalejandro/motivame/MotivameApplication.kt
Archivo normal
45
app/src/main/java/com/manalejandro/motivame/MotivameApplication.kt
Archivo normal
@@ -0,0 +1,45 @@
|
|||||||
|
package com.manalejandro.motivame
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.os.Build
|
||||||
|
import com.manalejandro.motivame.notifications.NotificationHelper
|
||||||
|
|
||||||
|
class MotivameApplication : Application() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
// Limpiar canales obsoletos de versiones anteriores
|
||||||
|
nm.deleteNotificationChannel("motivame_channel")
|
||||||
|
nm.deleteNotificationChannel(NotificationHelper.CHANNEL_ID_NO_SOUND)
|
||||||
|
|
||||||
|
// Canal con sonido: crear solo si no existe aún
|
||||||
|
if (nm.getNotificationChannel(NotificationHelper.CHANNEL_ID_SOUND) == null) {
|
||||||
|
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
val soundChannel = NotificationChannel(
|
||||||
|
NotificationHelper.CHANNEL_ID_SOUND,
|
||||||
|
getString(R.string.notification_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
).apply {
|
||||||
|
description = getString(R.string.notification_channel_description)
|
||||||
|
enableVibration(true)
|
||||||
|
vibrationPattern = longArrayOf(0, 500, 250, 500)
|
||||||
|
setSound(
|
||||||
|
soundUri,
|
||||||
|
AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
nm.createNotificationChannel(soundChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ data class Task(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val goals: List<String>,
|
val goals: List<String>,
|
||||||
val isActive: Boolean = true,
|
val isActive: Boolean = true,
|
||||||
val createdAt: Long = System.currentTimeMillis()
|
val createdAt: Long = System.currentTimeMillis(),
|
||||||
|
val dailyReminders: Int = 3, // Número de avisos por día (entre 9:00 y 21:00)
|
||||||
|
val repeatEveryDays: Int = 3 // Cada cuántos días se repite el ciclo de avisos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class TaskRepository(private val context: Context) {
|
|||||||
private val TASKS_KEY = stringPreferencesKey("tasks")
|
private val TASKS_KEY = stringPreferencesKey("tasks")
|
||||||
private val NOTIFICATION_ENABLED_KEY = stringPreferencesKey("notification_enabled")
|
private val NOTIFICATION_ENABLED_KEY = stringPreferencesKey("notification_enabled")
|
||||||
private val SOUND_ENABLED_KEY = stringPreferencesKey("sound_enabled")
|
private val SOUND_ENABLED_KEY = stringPreferencesKey("sound_enabled")
|
||||||
|
private val LANGUAGE_KEY = stringPreferencesKey("language")
|
||||||
|
|
||||||
val DEFAULT_TASKS = listOf(
|
val DEFAULT_TASKS = listOf(
|
||||||
Task(
|
Task(
|
||||||
@@ -68,6 +69,11 @@ class TaskRepository(private val context: Context) {
|
|||||||
preferences[SOUND_ENABLED_KEY]?.toBoolean() ?: true
|
preferences[SOUND_ENABLED_KEY]?.toBoolean() ?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val language: Flow<String> = context.dataStore.data
|
||||||
|
.map { preferences ->
|
||||||
|
preferences[LANGUAGE_KEY] ?: "es"
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun saveTasks(tasks: List<Task>) {
|
suspend fun saveTasks(tasks: List<Task>) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[TASKS_KEY] = tasksToJson(tasks)
|
preferences[TASKS_KEY] = tasksToJson(tasks)
|
||||||
@@ -110,6 +116,12 @@ class TaskRepository(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setLanguage(languageCode: String) {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences[LANGUAGE_KEY] = languageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun tasksToJson(tasks: List<Task>): String {
|
private fun tasksToJson(tasks: List<Task>): String {
|
||||||
val jsonArray = JSONArray()
|
val jsonArray = JSONArray()
|
||||||
tasks.forEach { task ->
|
tasks.forEach { task ->
|
||||||
@@ -119,6 +131,8 @@ class TaskRepository(private val context: Context) {
|
|||||||
put("goals", JSONArray(task.goals))
|
put("goals", JSONArray(task.goals))
|
||||||
put("isActive", task.isActive)
|
put("isActive", task.isActive)
|
||||||
put("createdAt", task.createdAt)
|
put("createdAt", task.createdAt)
|
||||||
|
put("dailyReminders", task.dailyReminders)
|
||||||
|
put("repeatEveryDays", task.repeatEveryDays)
|
||||||
}
|
}
|
||||||
jsonArray.put(jsonObject)
|
jsonArray.put(jsonObject)
|
||||||
}
|
}
|
||||||
@@ -140,7 +154,9 @@ class TaskRepository(private val context: Context) {
|
|||||||
title = jsonObject.getString("title"),
|
title = jsonObject.getString("title"),
|
||||||
goals = goals,
|
goals = goals,
|
||||||
isActive = jsonObject.getBoolean("isActive"),
|
isActive = jsonObject.getBoolean("isActive"),
|
||||||
createdAt = jsonObject.getLong("createdAt")
|
createdAt = jsonObject.getLong("createdAt"),
|
||||||
|
dailyReminders = if (jsonObject.has("dailyReminders")) jsonObject.getInt("dailyReminders") else 3,
|
||||||
|
repeatEveryDays = if (jsonObject.has("repeatEveryDays")) jsonObject.getInt("repeatEveryDays") else 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.manalejandro.motivame.notifications
|
package com.manalejandro.motivame.notifications
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.media.Ringtone
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.manalejandro.motivame.MainActivity
|
import com.manalejandro.motivame.MainActivity
|
||||||
@@ -17,77 +15,57 @@ import kotlin.random.Random
|
|||||||
class NotificationHelper(private val context: Context) {
|
class NotificationHelper(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CHANNEL_ID = "motivame_channel"
|
const val CHANNEL_ID_SOUND = "motivame_channel_sound"
|
||||||
private const val CHANNEL_NAME = "Recordatorios de Tareas"
|
const val CHANNEL_ID_NO_SOUND = "motivame_channel_silent"
|
||||||
private const val CHANNEL_DESCRIPTION = "Notificaciones para recordarte tus tareas pendientes"
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
createNotificationChannel()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
|
||||||
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance).apply {
|
|
||||||
description = CHANNEL_DESCRIPTION
|
|
||||||
enableVibration(true)
|
|
||||||
vibrationPattern = longArrayOf(0, 500, 250, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
notificationManager.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendTaskReminder(task: Task, withSound: Boolean = true) {
|
fun sendTaskReminder(task: Task, withSound: Boolean = true) {
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context, 0, intent,
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
val motivationalMessage = if (task.goals.isNotEmpty()) {
|
val motivationalMessage = if (task.goals.isNotEmpty()) task.goals.random()
|
||||||
task.goals.random()
|
else context.getString(R.string.notification_default_message)
|
||||||
} else {
|
|
||||||
"¡Recuerda completar esta tarea!"
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
// La notificación siempre silenciosa: el sonido lo manejamos nosotros
|
||||||
|
// directamente con Ringtone para evitar cualquier interferencia del canal.
|
||||||
|
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID_SOUND)
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
.setContentTitle("⏰ ${task.title}")
|
.setContentTitle("⏰ ${task.title}")
|
||||||
.setContentText(motivationalMessage)
|
.setContentText(motivationalMessage)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(
|
.setStyle(
|
||||||
"📝 Tarea: ${task.title}\n\n🎯 Recuerda: $motivationalMessage"
|
NotificationCompat.BigTextStyle().bigText(
|
||||||
))
|
context.getString(R.string.notification_big_text, task.title, motivationalMessage)
|
||||||
|
)
|
||||||
|
)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setVibrate(longArrayOf(0, 500, 250, 500))
|
.setVibrate(longArrayOf(0, 500, 250, 500))
|
||||||
|
.setSilent(true) // siempre silenciosa a nivel canal
|
||||||
if (withSound) {
|
|
||||||
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
||||||
notificationBuilder.setSound(defaultSoundUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val notificationManager = NotificationManagerCompat.from(context)
|
NotificationManagerCompat.from(context)
|
||||||
notificationManager.notify(Random.nextInt(), notificationBuilder.build())
|
.notify(Random.nextInt(), notificationBuilder.build())
|
||||||
} catch (e: SecurityException) {
|
} catch (_: SecurityException) { }
|
||||||
// El usuario no ha concedido permisos de notificación
|
|
||||||
|
// Reproducir el sonido directamente si está activado
|
||||||
|
if (withSound) {
|
||||||
|
try {
|
||||||
|
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
val ringtone: Ringtone = RingtoneManager.getRingtone(context, soundUri)
|
||||||
|
ringtone.play()
|
||||||
|
} catch (_: Exception) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMotivationalReminder(tasks: List<Task>, withSound: Boolean = true) {
|
fun sendMotivationalReminder(tasks: List<Task>, withSound: Boolean = true) {
|
||||||
if (tasks.isEmpty()) return
|
if (tasks.isEmpty()) return
|
||||||
|
|
||||||
val activeTask = tasks.firstOrNull { it.isActive } ?: return
|
val activeTask = tasks.firstOrNull { it.isActive } ?: return
|
||||||
sendTaskReminder(activeTask, withSound)
|
sendTaskReminder(activeTask, withSound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.manalejandro.motivame.ui.screens
|
package com.manalejandro.motivame.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
@@ -12,8 +12,10 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.manalejandro.motivame.R
|
||||||
import com.manalejandro.motivame.data.Task
|
import com.manalejandro.motivame.data.Task
|
||||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||||
|
|
||||||
@@ -21,19 +23,30 @@ import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun AddTaskScreen(
|
fun AddTaskScreen(
|
||||||
viewModel: TaskViewModel,
|
viewModel: TaskViewModel,
|
||||||
onNavigateBack: () -> Unit
|
onNavigateBack: () -> Unit,
|
||||||
|
taskToEdit: Task? = null
|
||||||
) {
|
) {
|
||||||
var taskTitle by remember { mutableStateOf("") }
|
val isEditing = taskToEdit != null
|
||||||
|
var taskTitle by remember { mutableStateOf(taskToEdit?.title ?: "") }
|
||||||
var currentGoal by remember { mutableStateOf("") }
|
var currentGoal by remember { mutableStateOf("") }
|
||||||
var goals by remember { mutableStateOf(listOf<String>()) }
|
var goals by remember { mutableStateOf(taskToEdit?.goals ?: listOf()) }
|
||||||
|
var dailyReminders by remember { mutableStateOf(taskToEdit?.dailyReminders ?: 3) }
|
||||||
|
var repeatEveryDays by remember { mutableStateOf(taskToEdit?.repeatEveryDays ?: 3) }
|
||||||
|
|
||||||
|
BackHandler { onNavigateBack() }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Nueva Tarea") },
|
title = {
|
||||||
|
Text(
|
||||||
|
if (isEditing) stringResource(R.string.edit_task_title)
|
||||||
|
else stringResource(R.string.new_task_title)
|
||||||
|
)
|
||||||
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Volver")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_content_desc))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
@@ -61,7 +74,7 @@ fun AddTaskScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "📝 ¿Qué debes recordar?",
|
text = stringResource(R.string.what_to_remember),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
@@ -70,8 +83,8 @@ fun AddTaskScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = taskTitle,
|
value = taskTitle,
|
||||||
onValueChange = { taskTitle = it },
|
onValueChange = { taskTitle = it },
|
||||||
label = { Text("Título de la tarea") },
|
label = { Text(stringResource(R.string.task_title_label)) },
|
||||||
placeholder = { Text("Ej: Hacer ejercicio") },
|
placeholder = { Text(stringResource(R.string.task_title_placeholder)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
@@ -93,7 +106,7 @@ fun AddTaskScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "🎯 ¿Qué esperas alcanzar?",
|
text = stringResource(R.string.what_to_achieve),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
@@ -103,8 +116,8 @@ fun AddTaskScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = currentGoal,
|
value = currentGoal,
|
||||||
onValueChange = { currentGoal = it },
|
onValueChange = { currentGoal = it },
|
||||||
label = { Text("Nueva meta") },
|
label = { Text(stringResource(R.string.new_goal_label)) },
|
||||||
placeholder = { Text("Ej: Mejorar mi salud") },
|
placeholder = { Text(stringResource(R.string.new_goal_placeholder)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -116,7 +129,7 @@ fun AddTaskScreen(
|
|||||||
},
|
},
|
||||||
enabled = currentGoal.isNotBlank()
|
enabled = currentGoal.isNotBlank()
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = "Agregar meta")
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_goal_desc))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -127,7 +140,7 @@ fun AddTaskScreen(
|
|||||||
if (goals.isNotEmpty()) {
|
if (goals.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
text = "Metas agregadas:",
|
text = stringResource(R.string.goals_added),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@@ -172,7 +185,7 @@ fun AddTaskScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = "Eliminar meta",
|
contentDescription = stringResource(R.string.delete_goal_desc),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -181,17 +194,193 @@ fun AddTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.daily_reminders_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.daily_reminders_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { if (dailyReminders > 1) dailyReminders-- },
|
||||||
|
enabled = dailyReminders > 1
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowDown,
|
||||||
|
contentDescription = stringResource(R.string.decrease_desc),
|
||||||
|
tint = if (dailyReminders > 1) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "$dailyReminders",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (dailyReminders == 1) stringResource(R.string.reminder_singular)
|
||||||
|
else stringResource(R.string.reminder_plural),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { if (dailyReminders < 10) dailyReminders++ },
|
||||||
|
enabled = dailyReminders < 10
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowUp,
|
||||||
|
contentDescription = stringResource(R.string.increase_desc),
|
||||||
|
tint = if (dailyReminders < 10) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dailyReminders > 1) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
val intervalMinutes = 720 / (dailyReminders - 1)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.interval_hint, formatInterval(intervalMinutes)),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.repeat_days_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.repeat_days_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { if (repeatEveryDays > 1) repeatEveryDays-- },
|
||||||
|
enabled = repeatEveryDays > 1
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowDown,
|
||||||
|
contentDescription = stringResource(R.string.decrease_days_desc),
|
||||||
|
tint = if (repeatEveryDays > 1) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "$repeatEveryDays",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (repeatEveryDays == 1) stringResource(R.string.day_singular)
|
||||||
|
else stringResource(R.string.day_plural),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { if (repeatEveryDays < 30) repeatEveryDays++ },
|
||||||
|
enabled = repeatEveryDays < 30
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowUp,
|
||||||
|
contentDescription = stringResource(R.string.increase_days_desc),
|
||||||
|
tint = if (repeatEveryDays < 30) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = if (repeatEveryDays == 1) stringResource(R.string.repeat_every_day)
|
||||||
|
else stringResource(R.string.repeat_every_n_days, repeatEveryDays),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (taskTitle.isNotBlank()) {
|
if (taskTitle.isNotBlank()) {
|
||||||
val newTask = Task(
|
val existing = taskToEdit
|
||||||
title = taskTitle.trim(),
|
if (existing != null) {
|
||||||
goals = goals,
|
viewModel.updateTask(
|
||||||
isActive = true
|
existing.copy(
|
||||||
)
|
title = taskTitle.trim(),
|
||||||
viewModel.addTask(newTask)
|
goals = goals,
|
||||||
|
dailyReminders = dailyReminders,
|
||||||
|
repeatEveryDays = repeatEveryDays
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val newTask = Task(
|
||||||
|
title = taskTitle.trim(),
|
||||||
|
goals = goals,
|
||||||
|
isActive = true,
|
||||||
|
dailyReminders = dailyReminders,
|
||||||
|
repeatEveryDays = repeatEveryDays
|
||||||
|
)
|
||||||
|
viewModel.addTask(newTask)
|
||||||
|
}
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -204,7 +393,8 @@ fun AddTaskScreen(
|
|||||||
Icon(Icons.Default.Check, contentDescription = null)
|
Icon(Icons.Default.Check, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Guardar Tarea",
|
text = if (isEditing) stringResource(R.string.update_task)
|
||||||
|
else stringResource(R.string.save_task),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -214,6 +404,12 @@ fun AddTaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatInterval(minutes: Int): String {
|
||||||
|
return if (minutes >= 60) {
|
||||||
|
val h = minutes / 60
|
||||||
|
val m = minutes % 60
|
||||||
|
if (m == 0) "${h}h" else "${h}h ${m}min"
|
||||||
|
} else {
|
||||||
|
"${minutes}min"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.manalejandro.motivame.ui.screens
|
package com.manalejandro.motivame.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -13,8 +15,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.manalejandro.motivame.R
|
||||||
import com.manalejandro.motivame.data.Task
|
import com.manalejandro.motivame.data.Task
|
||||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||||
|
|
||||||
@@ -23,7 +27,8 @@ import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
|||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
viewModel: TaskViewModel,
|
viewModel: TaskViewModel,
|
||||||
onNavigateToAddTask: () -> Unit,
|
onNavigateToAddTask: () -> Unit,
|
||||||
onNavigateToSettings: () -> Unit
|
onNavigateToSettings: () -> Unit,
|
||||||
|
onEditTask: (Task) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val tasks by viewModel.tasks.collectAsState()
|
val tasks by viewModel.tasks.collectAsState()
|
||||||
|
|
||||||
@@ -32,14 +37,14 @@ fun MainScreen(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
"Motívame",
|
stringResource(R.string.app_name),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onNavigateToSettings) {
|
IconButton(onClick = onNavigateToSettings) {
|
||||||
Icon(Icons.Default.Settings, contentDescription = "Configuración")
|
Icon(Icons.Default.Settings, contentDescription = stringResource(R.string.settings_content_desc))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
@@ -53,7 +58,7 @@ fun MainScreen(
|
|||||||
onClick = onNavigateToAddTask,
|
onClick = onNavigateToAddTask,
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = "Agregar tarea")
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_task_content_desc))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@@ -71,7 +76,8 @@ fun MainScreen(
|
|||||||
TaskCard(
|
TaskCard(
|
||||||
task = task,
|
task = task,
|
||||||
onToggleActive = { viewModel.updateTask(task.copy(isActive = !task.isActive)) },
|
onToggleActive = { viewModel.updateTask(task.copy(isActive = !task.isActive)) },
|
||||||
onDelete = { viewModel.deleteTask(task.id) }
|
onDelete = { viewModel.deleteTask(task.id) },
|
||||||
|
onEdit = { onEditTask(task) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,16 +85,23 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskCard(
|
fun TaskCard(
|
||||||
task: Task,
|
task: Task,
|
||||||
onToggleActive: () -> Unit,
|
onToggleActive: () -> Unit,
|
||||||
onDelete: () -> Unit
|
onDelete: () -> Unit,
|
||||||
|
onEdit: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = onEdit
|
||||||
|
),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -98,8 +111,10 @@ fun TaskCard(
|
|||||||
.background(
|
.background(
|
||||||
brush = Brush.verticalGradient(
|
brush = Brush.verticalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
MaterialTheme.colorScheme.surfaceVariant,
|
if (task.isActive) MaterialTheme.colorScheme.surfaceVariant
|
||||||
MaterialTheme.colorScheme.surface
|
else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||||
|
if (task.isActive) MaterialTheme.colorScheme.surface
|
||||||
|
else MaterialTheme.colorScheme.surface.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -117,30 +132,51 @@ fun TaskCard(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Star,
|
imageVector = Icons.Default.Star,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = if (task.isActive) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Column {
|
||||||
text = task.title,
|
Text(
|
||||||
style = MaterialTheme.typography.titleLarge,
|
text = task.title,
|
||||||
fontWeight = FontWeight.Bold,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
color = if (task.isActive) MaterialTheme.colorScheme.onSurface
|
||||||
|
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.long_press_hint),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
// Botón pausa/reanudar
|
||||||
IconButton(onClick = onToggleActive) {
|
IconButton(onClick = onToggleActive) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (task.isActive) Icons.Default.Check else Icons.Default.Close,
|
imageVector = if (task.isActive) Icons.Default.Pause else Icons.Default.PlayArrow,
|
||||||
contentDescription = "Toggle activo",
|
contentDescription = if (task.isActive) stringResource(R.string.pause_task_desc)
|
||||||
tint = if (task.isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
else stringResource(R.string.resume_task_desc),
|
||||||
|
tint = if (task.isActive) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.tertiary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Botón editar
|
||||||
|
IconButton(onClick = onEdit) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Edit,
|
||||||
|
contentDescription = stringResource(R.string.edit_task_title),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Botón eliminar
|
||||||
IconButton(onClick = { showDeleteDialog = true }) {
|
IconButton(onClick = { showDeleteDialog = true }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = "Eliminar",
|
contentDescription = stringResource(R.string.delete_task_desc),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -150,10 +186,11 @@ fun TaskCard(
|
|||||||
if (task.goals.isNotEmpty()) {
|
if (task.goals.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "🎯 Metas:",
|
text = stringResource(R.string.goals_label),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = if (task.isActive) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
@@ -165,18 +202,48 @@ fun TaskCard(
|
|||||||
Text(
|
Text(
|
||||||
text = "•",
|
text = "•",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = if (task.isActive) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = goal,
|
text = goal,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = if (task.isActive) MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resumen de avisos
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(6.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer.copy(alpha = if (task.isActive) 1f else 0.4f))
|
||||||
|
.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Notifications,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
|
tint = if (task.isActive) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
else MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = if (task.repeatEveryDays == 1)
|
||||||
|
stringResource(R.string.task_summary_reminders_daily, task.dailyReminders)
|
||||||
|
else
|
||||||
|
stringResource(R.string.task_summary_reminders, task.dailyReminders, task.repeatEveryDays),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = if (task.isActive) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
else MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!task.isActive) {
|
if (!task.isActive) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Box(
|
Box(
|
||||||
@@ -186,7 +253,7 @@ fun TaskCard(
|
|||||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "⏸️ Pausada",
|
text = stringResource(R.string.task_paused),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer
|
color = MaterialTheme.colorScheme.onErrorContainer
|
||||||
)
|
)
|
||||||
@@ -198,8 +265,8 @@ fun TaskCard(
|
|||||||
if (showDeleteDialog) {
|
if (showDeleteDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showDeleteDialog = false },
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
title = { Text("Eliminar tarea") },
|
title = { Text(stringResource(R.string.delete_task_title)) },
|
||||||
text = { Text("¿Estás seguro de que quieres eliminar '${task.title}'?") },
|
text = { Text(stringResource(R.string.delete_task_confirm, task.title)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -207,12 +274,12 @@ fun TaskCard(
|
|||||||
showDeleteDialog = false
|
showDeleteDialog = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("Eliminar", color = MaterialTheme.colorScheme.error)
|
Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showDeleteDialog = false }) {
|
TextButton(onClick = { showDeleteDialog = false }) {
|
||||||
Text("Cancelar")
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -236,17 +303,16 @@ fun EmptyState(modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "¡Comienza tu viaje!",
|
text = stringResource(R.string.empty_state_title),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Agrega tu primera tarea y metas para mantenerte motivado",
|
text = stringResource(R.string.empty_state_subtitle),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package com.manalejandro.motivame.ui.screens
|
package com.manalejandro.motivame.ui.screens
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -15,12 +20,15 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.manalejandro.motivame.data.TaskRepository
|
import com.manalejandro.motivame.R
|
||||||
import com.manalejandro.motivame.notifications.NotificationHelper
|
import com.manalejandro.motivame.notifications.NotificationHelper
|
||||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||||
|
import com.manalejandro.motivame.util.LocaleHelper
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -34,6 +42,9 @@ fun SettingsScreen(
|
|||||||
val notificationEnabled by viewModel.notificationEnabled.collectAsState()
|
val notificationEnabled by viewModel.notificationEnabled.collectAsState()
|
||||||
val soundEnabled by viewModel.soundEnabled.collectAsState()
|
val soundEnabled by viewModel.soundEnabled.collectAsState()
|
||||||
val tasks by viewModel.tasks.collectAsState()
|
val tasks by viewModel.tasks.collectAsState()
|
||||||
|
val currentLanguage by viewModel.language.collectAsState()
|
||||||
|
|
||||||
|
BackHandler { onNavigateBack() }
|
||||||
|
|
||||||
var hasNotificationPermission by remember {
|
var hasNotificationPermission by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -54,13 +65,15 @@ fun SettingsScreen(
|
|||||||
hasNotificationPermission = isGranted
|
hasNotificationPermission = isGranted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var languageMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Configuración") },
|
title = { Text(stringResource(R.string.settings_title)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Volver")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_content_desc))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
@@ -77,6 +90,7 @@ fun SettingsScreen(
|
|||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
|
// --- Idioma ---
|
||||||
item {
|
item {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -88,7 +102,87 @@ fun SettingsScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "🔔 Notificaciones",
|
text = stringResource(R.string.language_section),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.language_desc),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
val selectedLang = LocaleHelper.SUPPORTED_LANGUAGES
|
||||||
|
.firstOrNull { it.code == currentLanguage }
|
||||||
|
?: LocaleHelper.SUPPORTED_LANGUAGES.first()
|
||||||
|
|
||||||
|
Box {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { languageMenuExpanded = true },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${selectedLang.flag} ${selectedLang.nativeName}",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Icon(Icons.Default.ArrowDropDown, contentDescription = null)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = languageMenuExpanded,
|
||||||
|
onDismissRequest = { languageMenuExpanded = false }
|
||||||
|
) {
|
||||||
|
LocaleHelper.SUPPORTED_LANGUAGES.forEach { lang ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(text = lang.flag, style = MaterialTheme.typography.bodyLarge)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(text = lang.nativeName)
|
||||||
|
if (lang.code == currentLanguage) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
languageMenuExpanded = false
|
||||||
|
if (lang.code != currentLanguage) {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.setLanguage(lang.code)
|
||||||
|
// Recrear la Activity para aplicar el nuevo locale
|
||||||
|
(context as? Activity)?.recreate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Notificaciones ---
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.notifications_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
@@ -102,12 +196,12 @@ fun SettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Recordatorios diarios",
|
text = stringResource(R.string.daily_reminders_setting),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Recibe notificaciones para motivarte",
|
text = stringResource(R.string.daily_reminders_desc),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -135,12 +229,12 @@ fun SettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Sonido",
|
text = stringResource(R.string.sound_setting),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Reproducir sonido con las notificaciones",
|
text = stringResource(R.string.sound_desc),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -154,6 +248,7 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Prueba ---
|
||||||
item {
|
item {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -165,14 +260,14 @@ fun SettingsScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "🧪 Prueba",
|
text = stringResource(R.string.test_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Envía una notificación de prueba para verificar que todo funciona correctamente",
|
text = stringResource(R.string.test_desc),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -194,13 +289,13 @@ fun SettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Notifications, contentDescription = null)
|
Icon(Icons.Default.Notifications, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text("Enviar notificación de prueba")
|
Text(stringResource(R.string.send_test_notification))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.isEmpty()) {
|
if (tasks.isEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "⚠️ Agrega al menos una tarea para probar las notificaciones",
|
text = stringResource(R.string.add_task_to_test),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
@@ -209,6 +304,7 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Sobre la app ---
|
||||||
item {
|
item {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -223,29 +319,86 @@ fun SettingsScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "ℹ️ Sobre la app",
|
text = stringResource(R.string.about_section),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Motívame v1.0",
|
text = stringResource(R.string.app_version),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Tu compañero para mantener la motivación en tus tareas diarias",
|
text = stringResource(R.string.app_description),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f)
|
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
HorizontalDivider(color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.2f))
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Desarrollado por
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.developed_by) + " ",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.developer_url),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://manalejandro.com"))
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Repositorio GitHub
|
||||||
|
val githubUrl = stringResource(R.string.github_url)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Code,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.github_label) + ": ",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.github_url),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubUrl))
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.manalejandro.motivame.data.Task
|
import com.manalejandro.motivame.data.Task
|
||||||
import com.manalejandro.motivame.data.TaskRepository
|
import com.manalejandro.motivame.data.TaskRepository
|
||||||
|
import com.manalejandro.motivame.widget.MotivameWidget
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -23,6 +24,13 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private val _soundEnabled = MutableStateFlow(true)
|
private val _soundEnabled = MutableStateFlow(true)
|
||||||
val soundEnabled: StateFlow<Boolean> = _soundEnabled.asStateFlow()
|
val soundEnabled: StateFlow<Boolean> = _soundEnabled.asStateFlow()
|
||||||
|
|
||||||
|
private val _language = MutableStateFlow("es")
|
||||||
|
val language: StateFlow<String> = _language.asStateFlow()
|
||||||
|
|
||||||
|
/** Callback que se invoca tras cualquier cambio en las tareas para reprogramar avisos.
|
||||||
|
* Recibe el valor actual de notificationEnabled para evitar condiciones de carrera con DataStore. */
|
||||||
|
var onRescheduleReminders: ((notificationsEnabled: Boolean) -> Unit)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadTasks()
|
loadTasks()
|
||||||
loadSettings()
|
loadSettings()
|
||||||
@@ -47,23 +55,34 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
_soundEnabled.value = enabled
|
_soundEnabled.value = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.language.collect { lang ->
|
||||||
|
_language.value = lang
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTask(task: Task) {
|
fun addTask(task: Task) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.addTask(task)
|
repository.addTask(task)
|
||||||
|
onRescheduleReminders?.invoke(_notificationEnabled.value)
|
||||||
|
MotivameWidget.requestUpdate(getApplication())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTask(task: Task) {
|
fun updateTask(task: Task) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.updateTask(task)
|
repository.updateTask(task)
|
||||||
|
onRescheduleReminders?.invoke(_notificationEnabled.value)
|
||||||
|
MotivameWidget.requestUpdate(getApplication())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteTask(taskId: String) {
|
fun deleteTask(taskId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.deleteTask(taskId)
|
repository.deleteTask(taskId)
|
||||||
|
onRescheduleReminders?.invoke(_notificationEnabled.value)
|
||||||
|
MotivameWidget.requestUpdate(getApplication())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +90,8 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.setNotificationEnabled(enabled)
|
repository.setNotificationEnabled(enabled)
|
||||||
_notificationEnabled.value = enabled
|
_notificationEnabled.value = enabled
|
||||||
|
// Pasamos `enabled` directamente para no releer DataStore
|
||||||
|
onRescheduleReminders?.invoke(enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,5 +101,9 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
_soundEnabled.value = enabled
|
_soundEnabled.value = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
suspend fun setLanguage(languageCode: String) {
|
||||||
|
repository.setLanguage(languageCode)
|
||||||
|
_language.value = languageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
35
app/src/main/java/com/manalejandro/motivame/util/LocaleHelper.kt
Archivo normal
35
app/src/main/java/com/manalejandro/motivame/util/LocaleHelper.kt
Archivo normal
@@ -0,0 +1,35 @@
|
|||||||
|
package com.manalejandro.motivame.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object LocaleHelper {
|
||||||
|
|
||||||
|
data class Language(val code: String, val flag: String, val nativeName: String)
|
||||||
|
|
||||||
|
val SUPPORTED_LANGUAGES = listOf(
|
||||||
|
Language("es", "🇪🇸", "Español"),
|
||||||
|
Language("en", "🇬🇧", "English"),
|
||||||
|
Language("zh", "🇨🇳", "中文"),
|
||||||
|
Language("fr", "🇫🇷", "Français"),
|
||||||
|
Language("de", "🇩🇪", "Deutsch"),
|
||||||
|
Language("pt", "🇵🇹", "Português"),
|
||||||
|
Language("ja", "🇯🇵", "日本語"),
|
||||||
|
Language("ko", "🇰🇷", "한국어")
|
||||||
|
)
|
||||||
|
|
||||||
|
fun applyLocale(context: Context, languageCode: String): Context {
|
||||||
|
val locale = Locale(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
val config = Configuration(context.resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
return context.createConfigurationContext(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrap(context: Context, languageCode: String): Context {
|
||||||
|
if (languageCode.isEmpty()) return context
|
||||||
|
return applyLocale(context, languageCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
198
app/src/main/java/com/manalejandro/motivame/widget/MotivameWidget.kt
Archivo normal
198
app/src/main/java/com/manalejandro/motivame/widget/MotivameWidget.kt
Archivo normal
@@ -0,0 +1,198 @@
|
|||||||
|
package com.manalejandro.motivame.widget
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import com.manalejandro.motivame.MainActivity
|
||||||
|
import com.manalejandro.motivame.R
|
||||||
|
import com.manalejandro.motivame.data.Task
|
||||||
|
import com.manalejandro.motivame.data.TaskRepository
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MotivameWidget : AppWidgetProvider() {
|
||||||
|
|
||||||
|
override fun onUpdate(
|
||||||
|
context: Context,
|
||||||
|
appWidgetManager: AppWidgetManager,
|
||||||
|
appWidgetIds: IntArray
|
||||||
|
) {
|
||||||
|
appWidgetIds.forEach { updateWidget(context, appWidgetManager, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAppWidgetOptionsChanged(
|
||||||
|
context: Context,
|
||||||
|
appWidgetManager: AppWidgetManager,
|
||||||
|
appWidgetId: Int,
|
||||||
|
newOptions: Bundle
|
||||||
|
) {
|
||||||
|
updateWidget(context, appWidgetManager, appWidgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
super.onReceive(context, intent)
|
||||||
|
if (intent.action == ACTION_REFRESH) {
|
||||||
|
val mgr = AppWidgetManager.getInstance(context)
|
||||||
|
mgr.getAppWidgetIds(ComponentName(context, MotivameWidget::class.java))
|
||||||
|
.forEach { updateWidget(context, mgr, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_REFRESH = "com.manalejandro.motivame.WIDGET_REFRESH"
|
||||||
|
|
||||||
|
// ── Número de tareas según altura real (MIN_HEIGHT) ─────────────
|
||||||
|
// ~1 fila ≈ 74dp, ~2 filas ≈ 148dp, ~3 filas ≈ 222dp, ~4 filas ≈ 296dp
|
||||||
|
private fun taskCount(heightDp: Int) = when {
|
||||||
|
heightDp >= 220 -> 3
|
||||||
|
heightDp >= 145 -> 2
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Número de metas por tarea según espacio disponible ───────────
|
||||||
|
// Se divide la altura disponible entre el número de tareas para estimar
|
||||||
|
// el espacio por tarea y decidir cuántas metas caben.
|
||||||
|
private fun goalsPerTask(heightDp: Int, tasks: Int): Int {
|
||||||
|
val spacePerTask = heightDp / tasks
|
||||||
|
return when {
|
||||||
|
spacePerTask >= 160 -> 3 // mucho espacio → 3 metas
|
||||||
|
spacePerTask >= 100 -> 2 // espacio medio → 2 metas
|
||||||
|
else -> 1 // poco espacio → 1 meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun layoutFor(tasks: Int) = when (tasks) {
|
||||||
|
3 -> R.layout.widget_motivame_large
|
||||||
|
2 -> R.layout.widget_motivame_medium
|
||||||
|
else -> R.layout.widget_motivame_small
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs agrupados por tarea y slot de meta
|
||||||
|
private val TITLE_IDS = intArrayOf(
|
||||||
|
R.id.widget_task_title, R.id.widget_task2_title, R.id.widget_task3_title
|
||||||
|
)
|
||||||
|
private val GOAL_IDS = arrayOf(
|
||||||
|
intArrayOf(R.id.widget_t1_goal1, R.id.widget_t1_goal2, R.id.widget_t1_goal3),
|
||||||
|
intArrayOf(R.id.widget_t2_goal1, R.id.widget_t2_goal2, R.id.widget_t2_goal3),
|
||||||
|
intArrayOf(R.id.widget_t3_goal1, R.id.widget_t3_goal2, R.id.widget_t3_goal3)
|
||||||
|
)
|
||||||
|
private val CHIP_IDS = intArrayOf(
|
||||||
|
R.id.widget_chip1, R.id.widget_chip2, R.id.widget_chip3
|
||||||
|
)
|
||||||
|
|
||||||
|
fun updateWidget(context: Context, mgr: AppWidgetManager, widgetId: Int) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val options = mgr.getAppWidgetOptions(widgetId)
|
||||||
|
// MIN_HEIGHT = altura real actual del widget en el launcher
|
||||||
|
val heightDp = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 110)
|
||||||
|
val numTasks = taskCount(heightDp)
|
||||||
|
val numGoals = goalsPerTask(heightDp, numTasks)
|
||||||
|
val showChip = numTasks == 3 // chip solo en layout grande
|
||||||
|
|
||||||
|
val activeTasks = TaskRepository(context).tasks.first().filter { it.isActive }
|
||||||
|
val views = RemoteViews(context.packageName, layoutFor(numTasks))
|
||||||
|
|
||||||
|
// Click → abrir app
|
||||||
|
views.setOnClickPendingIntent(
|
||||||
|
R.id.widget_root,
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context, widgetId,
|
||||||
|
Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
},
|
||||||
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
views.setTextViewText(R.id.widget_hint,
|
||||||
|
context.getString(R.string.widget_tap_to_open))
|
||||||
|
|
||||||
|
if (activeTasks.isEmpty()) {
|
||||||
|
views.setTextViewText(R.id.widget_status, "○")
|
||||||
|
views.setTextViewText(R.id.widget_task_title, "Motívame")
|
||||||
|
views.setTextViewText(R.id.widget_t1_goal1,
|
||||||
|
context.getString(R.string.widget_no_tasks))
|
||||||
|
// ocultar metas 2 y 3 del slot 1
|
||||||
|
views.setViewVisibility(R.id.widget_t1_goal2, View.GONE)
|
||||||
|
views.setViewVisibility(R.id.widget_t1_goal3, View.GONE)
|
||||||
|
// ocultar slots extra
|
||||||
|
if (numTasks >= 2) hideTaskSlot(views, 1, showChip)
|
||||||
|
if (numTasks >= 3) hideTaskSlot(views, 2, showChip)
|
||||||
|
} else {
|
||||||
|
views.setTextViewText(R.id.widget_status, "●")
|
||||||
|
for (slot in 0 until numTasks) {
|
||||||
|
val task = activeTasks.getOrNull(slot)
|
||||||
|
fillTaskSlot(context, views, slot, task, numGoals, showChip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.updateAppWidget(widgetId, views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillTaskSlot(
|
||||||
|
context: Context,
|
||||||
|
views: RemoteViews,
|
||||||
|
slot: Int, // 0-based
|
||||||
|
task: Task?,
|
||||||
|
numGoals: Int,
|
||||||
|
showChip: Boolean
|
||||||
|
) {
|
||||||
|
val titleId = TITLE_IDS[slot]
|
||||||
|
val goalIds = GOAL_IDS[slot]
|
||||||
|
|
||||||
|
if (task == null) {
|
||||||
|
hideTaskSlot(views, slot, showChip)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
views.setViewVisibility(titleId, View.VISIBLE)
|
||||||
|
views.setTextViewText(titleId, task.title)
|
||||||
|
|
||||||
|
// Rellenar metas con opacidad decreciente; ocultar las que excedan numGoals
|
||||||
|
val goals = task.goals
|
||||||
|
for (i in 0..2) {
|
||||||
|
val goalId = goalIds[i]
|
||||||
|
if (i < numGoals && i < goals.size) {
|
||||||
|
views.setViewVisibility(goalId, View.VISIBLE)
|
||||||
|
views.setTextViewText(goalId, "🎯 ${goals[i]}")
|
||||||
|
} else {
|
||||||
|
views.setViewVisibility(goalId, View.GONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chip de avisos (solo layout grande)
|
||||||
|
if (showChip) {
|
||||||
|
val chipId = CHIP_IDS[slot]
|
||||||
|
val chipText = if (task.repeatEveryDays == 1)
|
||||||
|
context.getString(R.string.task_summary_reminders_daily, task.dailyReminders)
|
||||||
|
else
|
||||||
|
context.getString(R.string.task_summary_reminders, task.dailyReminders, task.repeatEveryDays)
|
||||||
|
views.setViewVisibility(chipId, View.VISIBLE)
|
||||||
|
views.setTextViewText(chipId, "🔔 $chipText")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideTaskSlot(views: RemoteViews, slot: Int, showChip: Boolean) {
|
||||||
|
views.setViewVisibility(TITLE_IDS[slot], View.GONE)
|
||||||
|
GOAL_IDS[slot].forEach { views.setViewVisibility(it, View.GONE) }
|
||||||
|
if (showChip) views.setViewVisibility(CHIP_IDS[slot], View.GONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestUpdate(context: Context) {
|
||||||
|
context.sendBroadcast(
|
||||||
|
Intent(context, MotivameWidget::class.java).apply {
|
||||||
|
action = ACTION_REFRESH
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@ import androidx.work.CoroutineWorker
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.manalejandro.motivame.data.TaskRepository
|
import com.manalejandro.motivame.data.TaskRepository
|
||||||
import com.manalejandro.motivame.notifications.NotificationHelper
|
import com.manalejandro.motivame.notifications.NotificationHelper
|
||||||
|
import com.manalejandro.motivame.widget.MotivameWidget
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
class DailyReminderWorker(
|
class DailyReminderWorker(
|
||||||
@@ -12,18 +13,35 @@ class DailyReminderWorker(
|
|||||||
params: WorkerParameters
|
params: WorkerParameters
|
||||||
) : CoroutineWorker(context, params) {
|
) : CoroutineWorker(context, params) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_TASK_ID = "task_id"
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val repository = TaskRepository(applicationContext)
|
val repository = TaskRepository(applicationContext)
|
||||||
val notificationHelper = NotificationHelper(applicationContext)
|
val notificationHelper = NotificationHelper(applicationContext)
|
||||||
|
|
||||||
val tasks = repository.tasks.first()
|
|
||||||
val notificationEnabled = repository.notificationEnabled.first()
|
val notificationEnabled = repository.notificationEnabled.first()
|
||||||
val soundEnabled = repository.soundEnabled.first()
|
if (!notificationEnabled) return Result.success()
|
||||||
|
|
||||||
if (notificationEnabled && tasks.isNotEmpty()) {
|
val soundEnabled = repository.soundEnabled.first()
|
||||||
notificationHelper.sendMotivationalReminder(tasks, soundEnabled)
|
val taskId = inputData.getString(KEY_TASK_ID)
|
||||||
|
|
||||||
|
val tasks = repository.tasks.first()
|
||||||
|
|
||||||
|
val taskToNotify = if (taskId != null) {
|
||||||
|
tasks.firstOrNull { it.id == taskId && it.isActive }
|
||||||
|
} else {
|
||||||
|
tasks.firstOrNull { it.isActive }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taskToNotify?.let {
|
||||||
|
notificationHelper.sendTaskReminder(it, soundEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refrescar el widget con la meta actualizada
|
||||||
|
MotivameWidget.requestUpdate(applicationContext)
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
app/src/main/res/drawable/widget_background.xml
Archivo normal
11
app/src/main/res/drawable/widget_background.xml
Archivo normal
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:startColor="#6366F1"
|
||||||
|
android:endColor="#8B5CF6"
|
||||||
|
android:angle="135"
|
||||||
|
android:type="linear" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
|
|
||||||
7
app/src/main/res/drawable/widget_task_bg.xml
Archivo normal
7
app/src/main/res/drawable/widget_task_bg.xml
Archivo normal
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#26FFFFFF" />
|
||||||
|
<corners android:radius="10dp" />
|
||||||
|
</shape>
|
||||||
|
|
||||||
78
app/src/main/res/layout/widget_motivame.xml
Archivo normal
78
app/src/main/res/layout/widget_motivame.xml
Archivo normal
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_background"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<!-- Cabecera: icono + nombre de la app -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="⭐ Motívame"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:alpha="0.8" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="●"
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Título de la tarea -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Tarea"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="6dp" />
|
||||||
|
|
||||||
|
<!-- Meta aleatoria -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_goal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="🎯 Meta"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<!-- Pie: toca para abrir -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Toca para abrir →"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.55"
|
||||||
|
android:gravity="end"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
248
app/src/main/res/layout/widget_motivame_large.xml
Archivo normal
248
app/src/main/res/layout/widget_motivame_large.xml
Archivo normal
@@ -0,0 +1,248 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Layout GRANDE: 3 tareas · 3 metas por tarea + chip de avisos -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_background"
|
||||||
|
android:padding="14dp"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<!-- Cabecera -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="⭐ Motívame"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:alpha="0.8" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="●"
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 1 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_chip1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 2 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task2_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_chip2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 3 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task3_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t3_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t3_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t3_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_chip3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Pie -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:gravity="end" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
162
app/src/main/res/layout/widget_motivame_medium.xml
Archivo normal
162
app/src/main/res/layout/widget_motivame_medium.xml
Archivo normal
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Layout MEDIANO: 2 tareas · 2 metas por tarea -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_background"
|
||||||
|
android:padding="14dp"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<!-- Cabecera -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="⭐ Motívame"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:alpha="0.8" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="●"
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 1 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="6dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 2 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task2_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t2_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Pie -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:gravity="end" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
106
app/src/main/res/layout/widget_motivame_small.xml
Archivo normal
106
app/src/main/res/layout/widget_motivame_small.xml
Archivo normal
@@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Layout PEQUEÑO: 1 tarea · 1 meta visible -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_background"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<!-- Cabecera -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="6dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="⭐ Motívame"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:alpha="0.8" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="●"
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Tarea 1 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/widget_task_bg"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_task_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
|
<!-- Meta 1 (siempre visible) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:alpha="0.85"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<!-- Meta 2 (oculta en small) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:alpha="0.75"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- Meta 3 (oculta en small) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_t1_goal3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:alpha="0.65"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Pie -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:gravity="end"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
86
app/src/main/res/values-de/strings.xml
Archivo normal
86
app/src/main/res/values-de/strings.xml
Archivo normal
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Motivier mich</string>
|
||||||
|
<string name="notification_channel_name">Aufgabenerinnerungen</string>
|
||||||
|
<string name="notification_channel_description">Benachrichtigungen, die Sie an Ihre ausstehenden Aufgaben erinnern</string>
|
||||||
|
|
||||||
|
<string name="settings_content_desc">Einstellungen</string>
|
||||||
|
<string name="add_task_content_desc">Aufgabe hinzufügen</string>
|
||||||
|
<string name="empty_state_title">Beginne deine Reise!</string>
|
||||||
|
<string name="empty_state_subtitle">Füge deine erste Aufgabe und Ziele hinzu, um motiviert zu bleiben</string>
|
||||||
|
<string name="goals_label">🎯 Ziele:</string>
|
||||||
|
<string name="task_paused">⏸️ Pausiert</string>
|
||||||
|
<string name="toggle_active_desc">Aufgabe aktivieren/pausieren</string>
|
||||||
|
<string name="delete_task_desc">Löschen</string>
|
||||||
|
<string name="delete_task_title">Aufgabe löschen</string>
|
||||||
|
<string name="delete_task_confirm">Möchtest du \'%1$s\' wirklich löschen?</string>
|
||||||
|
<string name="delete">Löschen</string>
|
||||||
|
<string name="cancel">Abbrechen</string>
|
||||||
|
|
||||||
|
<string name="new_task_title">Neue Aufgabe</string>
|
||||||
|
<string name="back_content_desc">Zurück</string>
|
||||||
|
<string name="what_to_remember">📝 Was musst du dir merken?</string>
|
||||||
|
<string name="task_title_label">Aufgabentitel</string>
|
||||||
|
<string name="task_title_placeholder">z.B.: Sport treiben</string>
|
||||||
|
<string name="what_to_achieve">🎯 Was möchtest du erreichen?</string>
|
||||||
|
<string name="new_goal_label">Neues Ziel</string>
|
||||||
|
<string name="new_goal_placeholder">z.B.: Meine Gesundheit verbessern</string>
|
||||||
|
<string name="add_goal_desc">Ziel hinzufügen</string>
|
||||||
|
<string name="goals_added">Hinzugefügte Ziele:</string>
|
||||||
|
<string name="delete_goal_desc">Ziel löschen</string>
|
||||||
|
<string name="daily_reminders_title">🔔 Tägliche Erinnerungen</string>
|
||||||
|
<string name="daily_reminders_subtitle">Anzahl der Erinnerungen zwischen 9:00 und 21:00 Uhr</string>
|
||||||
|
<string name="reminder_singular">Erinnerung</string>
|
||||||
|
<string name="reminder_plural">Erinnerungen</string>
|
||||||
|
<string name="decrease_desc">Verringern</string>
|
||||||
|
<string name="increase_desc">Erhöhen</string>
|
||||||
|
<string name="interval_hint">⏱️ Eine Erinnerung alle %1$s ca.</string>
|
||||||
|
<string name="repeat_days_title">📅 Alle wie viele Tage</string>
|
||||||
|
<string name="repeat_days_subtitle">Tagesintervall zwischen jedem Erinnerungszyklus</string>
|
||||||
|
<string name="day_singular">Tag</string>
|
||||||
|
<string name="day_plural">Tage</string>
|
||||||
|
<string name="decrease_days_desc">Tage verringern</string>
|
||||||
|
<string name="increase_days_desc">Tage erhöhen</string>
|
||||||
|
<string name="repeat_every_day">🔁 Täglich Erinnerungen</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 Erinnerungen alle %1$d Tage, verteilt um Überschneidungen zu vermeiden</string>
|
||||||
|
<string name="save_task">Aufgabe speichern</string>
|
||||||
|
<string name="edit_task_title">Aufgabe bearbeiten</string>
|
||||||
|
<string name="update_task">Änderungen speichern</string>
|
||||||
|
<string name="pause_task_desc">Aufgabe pausieren</string>
|
||||||
|
<string name="resume_task_desc">Aufgabe fortsetzen</string>
|
||||||
|
<string name="long_press_hint">Lang drücken zum Bearbeiten</string>
|
||||||
|
<string name="task_summary_reminders">%1$d Erinnerung/Tag · alle %2$d Tage</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d Erinnerung/Tag · täglich</string>
|
||||||
|
|
||||||
|
<string name="settings_title">Einstellungen</string>
|
||||||
|
<string name="notifications_section">🔔 Benachrichtigungen</string>
|
||||||
|
<string name="daily_reminders_setting">Tägliche Erinnerungen</string>
|
||||||
|
<string name="daily_reminders_desc">Benachrichtigungen erhalten, um dich zu motivieren</string>
|
||||||
|
<string name="sound_setting">Ton</string>
|
||||||
|
<string name="sound_desc">Ton bei Benachrichtigungen abspielen</string>
|
||||||
|
<string name="test_section">🧪 Test</string>
|
||||||
|
<string name="test_desc">Eine Test-Benachrichtigung senden, um zu prüfen, ob alles funktioniert</string>
|
||||||
|
<string name="send_test_notification">Test-Benachrichtigung senden</string>
|
||||||
|
<string name="add_task_to_test">⚠️ Füge mindestens eine Aufgabe hinzu, um Benachrichtigungen zu testen</string>
|
||||||
|
<string name="about_section">ℹ️ Über die App</string>
|
||||||
|
<string name="app_version">Motivier mich v1.0</string>
|
||||||
|
<string name="app_description">Dein Begleiter, um in deinen täglichen Aufgaben motiviert zu bleiben</string>
|
||||||
|
<string name="developed_by">Entwickelt von</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">GitHub-Repository</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<string name="language_section">🌐 Sprache</string>
|
||||||
|
<string name="language_desc">Wähle die Sprache der Anwendung</string>
|
||||||
|
<string name="language_restart_hint">Die App wird neu gestartet, um die Sprache anzuwenden</string>
|
||||||
|
|
||||||
|
<string name="notification_default_message">Denke daran, diese Aufgabe abzuschließen!</string>
|
||||||
|
<string name="notification_big_text">📝 Aufgabe: %1$s\n\n🎯 Denk daran: %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">Zeigt deine aktive Aufgabe und ein motivierendes Ziel</string>
|
||||||
|
<string name="widget_no_tasks">Keine aktiven Aufgaben.\nÖffne Motivier mich, um eine hinzuzufügen.</string>
|
||||||
|
<string name="widget_tap_to_open">Tippen zum Öffnen →</string>
|
||||||
|
<string name="widget_active">aktiv</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
92
app/src/main/res/values-en/strings.xml
Archivo normal
92
app/src/main/res/values-en/strings.xml
Archivo normal
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Motivate Me</string>
|
||||||
|
<string name="notification_channel_name">Task Reminders</string>
|
||||||
|
<string name="notification_channel_description">Notifications to remind you of your pending tasks</string>
|
||||||
|
|
||||||
|
<!-- MainScreen -->
|
||||||
|
<string name="settings_content_desc">Settings</string>
|
||||||
|
<string name="add_task_content_desc">Add task</string>
|
||||||
|
<string name="empty_state_title">Start your journey!</string>
|
||||||
|
<string name="empty_state_subtitle">Add your first task and goals to stay motivated</string>
|
||||||
|
<string name="goals_label">🎯 Goals:</string>
|
||||||
|
<string name="task_paused">⏸️ Paused</string>
|
||||||
|
<string name="toggle_active_desc">Toggle task active</string>
|
||||||
|
<string name="delete_task_desc">Delete</string>
|
||||||
|
<string name="delete_task_title">Delete task</string>
|
||||||
|
<string name="delete_task_confirm">Are you sure you want to delete \'%1$s\'?</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
|
||||||
|
<!-- AddTaskScreen -->
|
||||||
|
<string name="new_task_title">New Task</string>
|
||||||
|
<string name="back_content_desc">Back</string>
|
||||||
|
<string name="what_to_remember">📝 What do you need to remember?</string>
|
||||||
|
<string name="task_title_label">Task title</string>
|
||||||
|
<string name="task_title_placeholder">E.g.: Exercise</string>
|
||||||
|
<string name="what_to_achieve">🎯 What do you expect to achieve?</string>
|
||||||
|
<string name="new_goal_label">New goal</string>
|
||||||
|
<string name="new_goal_placeholder">E.g.: Improve my health</string>
|
||||||
|
<string name="add_goal_desc">Add goal</string>
|
||||||
|
<string name="goals_added">Added goals:</string>
|
||||||
|
<string name="delete_goal_desc">Delete goal</string>
|
||||||
|
<string name="daily_reminders_title">🔔 Daily reminders</string>
|
||||||
|
<string name="daily_reminders_subtitle">Number of reminders between 9:00 and 21:00</string>
|
||||||
|
<string name="reminder_singular">reminder</string>
|
||||||
|
<string name="reminder_plural">reminders</string>
|
||||||
|
<string name="decrease_desc">Decrease</string>
|
||||||
|
<string name="increase_desc">Increase</string>
|
||||||
|
<string name="interval_hint">⏱️ One reminder every %1$s approx.</string>
|
||||||
|
<string name="repeat_days_title">📅 Every how many days</string>
|
||||||
|
<string name="repeat_days_subtitle">Interval of days between each reminder cycle</string>
|
||||||
|
<string name="day_singular">day</string>
|
||||||
|
<string name="day_plural">days</string>
|
||||||
|
<string name="decrease_days_desc">Decrease days</string>
|
||||||
|
<string name="increase_days_desc">Increase days</string>
|
||||||
|
<string name="repeat_every_day">🔁 Reminders every day</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 Reminders every %1$d days, spread out to avoid overlapping</string>
|
||||||
|
<string name="save_task">Save Task</string>
|
||||||
|
<string name="edit_task_title">Edit Task</string>
|
||||||
|
<string name="update_task">Save Changes</string>
|
||||||
|
<string name="pause_task_desc">Pause task</string>
|
||||||
|
<string name="resume_task_desc">Resume task</string>
|
||||||
|
<string name="long_press_hint">Long press to edit</string>
|
||||||
|
<string name="task_summary_reminders">%1$d reminder/day · every %2$d days</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d reminder/day · every day</string>
|
||||||
|
|
||||||
|
<!-- SettingsScreen -->
|
||||||
|
<string name="settings_title">Settings</string>
|
||||||
|
<string name="notifications_section">🔔 Notifications</string>
|
||||||
|
<string name="daily_reminders_setting">Daily reminders</string>
|
||||||
|
<string name="daily_reminders_desc">Receive notifications to motivate yourself</string>
|
||||||
|
<string name="sound_setting">Sound</string>
|
||||||
|
<string name="sound_desc">Play sound with notifications</string>
|
||||||
|
<string name="test_section">🧪 Test</string>
|
||||||
|
<string name="test_desc">Send a test notification to verify everything works correctly</string>
|
||||||
|
<string name="send_test_notification">Send test notification</string>
|
||||||
|
<string name="add_task_to_test">⚠️ Add at least one task to test notifications</string>
|
||||||
|
<string name="about_section">ℹ️ About the app</string>
|
||||||
|
<string name="app_version">Motivate Me v1.0</string>
|
||||||
|
<string name="app_description">Your companion to stay motivated on your daily tasks</string>
|
||||||
|
<string name="developed_by">Developed by</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">GitHub Repository</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<!-- Language selector -->
|
||||||
|
<string name="language_section">🌐 Language</string>
|
||||||
|
<string name="language_desc">Select the application language</string>
|
||||||
|
<string name="language_restart_hint">The app will restart to apply the language</string>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<string name="notification_default_message">Remember to complete this task!</string>
|
||||||
|
<string name="notification_big_text">📝 Task: %1$s\n\n🎯 Remember: %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">Shows your active task and a motivational goal</string>
|
||||||
|
<string name="widget_no_tasks">No active tasks.\nOpen Motivate Me to add one.</string>
|
||||||
|
<string name="widget_tap_to_open">Tap to open →</string>
|
||||||
|
<string name="widget_active">active</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
|
|
||||||
86
app/src/main/res/values-fr/strings.xml
Archivo normal
86
app/src/main/res/values-fr/strings.xml
Archivo normal
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Motivez-moi</string>
|
||||||
|
<string name="notification_channel_name">Rappels de tâches</string>
|
||||||
|
<string name="notification_channel_description">Notifications pour vous rappeler vos tâches en attente</string>
|
||||||
|
|
||||||
|
<string name="settings_content_desc">Paramètres</string>
|
||||||
|
<string name="add_task_content_desc">Ajouter une tâche</string>
|
||||||
|
<string name="empty_state_title">Commencez votre voyage !</string>
|
||||||
|
<string name="empty_state_subtitle">Ajoutez votre première tâche et vos objectifs pour rester motivé</string>
|
||||||
|
<string name="goals_label">🎯 Objectifs :</string>
|
||||||
|
<string name="task_paused">⏸️ En pause</string>
|
||||||
|
<string name="toggle_active_desc">Activer/mettre en pause la tâche</string>
|
||||||
|
<string name="delete_task_desc">Supprimer</string>
|
||||||
|
<string name="delete_task_title">Supprimer la tâche</string>
|
||||||
|
<string name="delete_task_confirm">Voulez-vous vraiment supprimer \'%1$s\' ?</string>
|
||||||
|
<string name="delete">Supprimer</string>
|
||||||
|
<string name="cancel">Annuler</string>
|
||||||
|
|
||||||
|
<string name="new_task_title">Nouvelle tâche</string>
|
||||||
|
<string name="back_content_desc">Retour</string>
|
||||||
|
<string name="what_to_remember">📝 Que devez-vous retenir ?</string>
|
||||||
|
<string name="task_title_label">Titre de la tâche</string>
|
||||||
|
<string name="task_title_placeholder">Ex : Faire du sport</string>
|
||||||
|
<string name="what_to_achieve">🎯 Qu\'espérez-vous accomplir ?</string>
|
||||||
|
<string name="new_goal_label">Nouvel objectif</string>
|
||||||
|
<string name="new_goal_placeholder">Ex : Améliorer ma santé</string>
|
||||||
|
<string name="add_goal_desc">Ajouter un objectif</string>
|
||||||
|
<string name="goals_added">Objectifs ajoutés :</string>
|
||||||
|
<string name="delete_goal_desc">Supprimer l\'objectif</string>
|
||||||
|
<string name="daily_reminders_title">🔔 Rappels quotidiens</string>
|
||||||
|
<string name="daily_reminders_subtitle">Nombre de rappels entre 9h00 et 21h00</string>
|
||||||
|
<string name="reminder_singular">rappel</string>
|
||||||
|
<string name="reminder_plural">rappels</string>
|
||||||
|
<string name="decrease_desc">Diminuer</string>
|
||||||
|
<string name="increase_desc">Augmenter</string>
|
||||||
|
<string name="interval_hint">⏱️ Un rappel toutes les %1$s environ.</string>
|
||||||
|
<string name="repeat_days_title">📅 Tous les combien de jours</string>
|
||||||
|
<string name="repeat_days_subtitle">Intervalle de jours entre chaque cycle de rappels</string>
|
||||||
|
<string name="day_singular">jour</string>
|
||||||
|
<string name="day_plural">jours</string>
|
||||||
|
<string name="decrease_days_desc">Réduire les jours</string>
|
||||||
|
<string name="increase_days_desc">Augmenter les jours</string>
|
||||||
|
<string name="repeat_every_day">🔁 Rappels tous les jours</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 Rappels tous les %1$d jours, répartis pour ne pas se chevaucher</string>
|
||||||
|
<string name="save_task">Enregistrer la tâche</string>
|
||||||
|
<string name="edit_task_title">Modifier la tâche</string>
|
||||||
|
<string name="update_task">Enregistrer les modifications</string>
|
||||||
|
<string name="pause_task_desc">Mettre en pause</string>
|
||||||
|
<string name="resume_task_desc">Reprendre</string>
|
||||||
|
<string name="long_press_hint">Appui long pour modifier</string>
|
||||||
|
<string name="task_summary_reminders">%1$d rappel/jour · tous les %2$d jours</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d rappel/jour · chaque jour</string>
|
||||||
|
|
||||||
|
<string name="settings_title">Paramètres</string>
|
||||||
|
<string name="notifications_section">🔔 Notifications</string>
|
||||||
|
<string name="daily_reminders_setting">Rappels quotidiens</string>
|
||||||
|
<string name="daily_reminders_desc">Recevoir des notifications pour vous motiver</string>
|
||||||
|
<string name="sound_setting">Son</string>
|
||||||
|
<string name="sound_desc">Jouer un son avec les notifications</string>
|
||||||
|
<string name="test_section">🧪 Test</string>
|
||||||
|
<string name="test_desc">Envoyer une notification de test pour vérifier que tout fonctionne</string>
|
||||||
|
<string name="send_test_notification">Envoyer une notification de test</string>
|
||||||
|
<string name="add_task_to_test">⚠️ Ajoutez au moins une tâche pour tester les notifications</string>
|
||||||
|
<string name="about_section">ℹ️ À propos</string>
|
||||||
|
<string name="app_version">Motivez-moi v1.0</string>
|
||||||
|
<string name="app_description">Votre compagnon pour rester motivé dans vos tâches quotidiennes</string>
|
||||||
|
<string name="developed_by">Développé par</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">Dépôt GitHub</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<string name="language_section">🌐 Langue</string>
|
||||||
|
<string name="language_desc">Sélectionnez la langue de l\'application</string>
|
||||||
|
<string name="language_restart_hint">L\'app redémarrera pour appliquer la langue</string>
|
||||||
|
|
||||||
|
<string name="notification_default_message">N\'oubliez pas de terminer cette tâche !</string>
|
||||||
|
<string name="notification_big_text">📝 Tâche : %1$s\n\n🎯 Rappel : %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">Affiche votre tâche active et un objectif motivationnel</string>
|
||||||
|
<string name="widget_no_tasks">Aucune tâche active.\nOuvrez Motivez-moi pour en ajouter une.</string>
|
||||||
|
<string name="widget_tap_to_open">Toucher pour ouvrir →</string>
|
||||||
|
<string name="widget_active">active</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
86
app/src/main/res/values-ja/strings.xml
Archivo normal
86
app/src/main/res/values-ja/strings.xml
Archivo normal
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">やる気アップ</string>
|
||||||
|
<string name="notification_channel_name">タスクリマインダー</string>
|
||||||
|
<string name="notification_channel_description">保留中のタスクを通知するお知らせ</string>
|
||||||
|
|
||||||
|
<string name="settings_content_desc">設定</string>
|
||||||
|
<string name="add_task_content_desc">タスクを追加</string>
|
||||||
|
<string name="empty_state_title">旅を始めよう!</string>
|
||||||
|
<string name="empty_state_subtitle">最初のタスクと目標を追加してモチベーションを維持しよう</string>
|
||||||
|
<string name="goals_label">🎯 目標:</string>
|
||||||
|
<string name="task_paused">⏸️ 一時停止中</string>
|
||||||
|
<string name="toggle_active_desc">タスクの有効/一時停止を切り替え</string>
|
||||||
|
<string name="delete_task_desc">削除</string>
|
||||||
|
<string name="delete_task_title">タスクを削除</string>
|
||||||
|
<string name="delete_task_confirm">\'%1$s\' を削除してもよろしいですか?</string>
|
||||||
|
<string name="delete">削除</string>
|
||||||
|
<string name="cancel">キャンセル</string>
|
||||||
|
|
||||||
|
<string name="new_task_title">新しいタスク</string>
|
||||||
|
<string name="back_content_desc">戻る</string>
|
||||||
|
<string name="what_to_remember">📝 何を覚えておく必要がありますか?</string>
|
||||||
|
<string name="task_title_label">タスクのタイトル</string>
|
||||||
|
<string name="task_title_placeholder">例:運動する</string>
|
||||||
|
<string name="what_to_achieve">🎯 何を達成したいですか?</string>
|
||||||
|
<string name="new_goal_label">新しい目標</string>
|
||||||
|
<string name="new_goal_placeholder">例:健康を改善する</string>
|
||||||
|
<string name="add_goal_desc">目標を追加</string>
|
||||||
|
<string name="goals_added">追加された目標:</string>
|
||||||
|
<string name="delete_goal_desc">目標を削除</string>
|
||||||
|
<string name="daily_reminders_title">🔔 毎日のリマインダー</string>
|
||||||
|
<string name="daily_reminders_subtitle">9:00〜21:00の間のリマインダー数</string>
|
||||||
|
<string name="reminder_singular">回のリマインダー</string>
|
||||||
|
<string name="reminder_plural">回のリマインダー</string>
|
||||||
|
<string name="decrease_desc">減らす</string>
|
||||||
|
<string name="increase_desc">増やす</string>
|
||||||
|
<string name="interval_hint">⏱️ 約%1$sごとに1回のリマインダー</string>
|
||||||
|
<string name="repeat_days_title">📅 何日ごとに繰り返す</string>
|
||||||
|
<string name="repeat_days_subtitle">各リマインダーサイクル間の日数</string>
|
||||||
|
<string name="day_singular">日</string>
|
||||||
|
<string name="day_plural">日</string>
|
||||||
|
<string name="decrease_days_desc">日数を減らす</string>
|
||||||
|
<string name="increase_days_desc">日数を増やす</string>
|
||||||
|
<string name="repeat_every_day">🔁 毎日リマインダー</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 %1$d日ごとにリマインダー、重複しないよう分散</string>
|
||||||
|
<string name="save_task">タスクを保存</string>
|
||||||
|
<string name="edit_task_title">タスクを編集</string>
|
||||||
|
<string name="update_task">変更を保存</string>
|
||||||
|
<string name="pause_task_desc">タスクを一時停止</string>
|
||||||
|
<string name="resume_task_desc">タスクを再開</string>
|
||||||
|
<string name="long_press_hint">長押しで編集</string>
|
||||||
|
<string name="task_summary_reminders">%1$d回/日 · %2$d日ごと</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d回/日 · 毎日</string>
|
||||||
|
|
||||||
|
<string name="settings_title">設定</string>
|
||||||
|
<string name="notifications_section">🔔 通知</string>
|
||||||
|
<string name="daily_reminders_setting">毎日のリマインダー</string>
|
||||||
|
<string name="daily_reminders_desc">モチベーションを高める通知を受け取る</string>
|
||||||
|
<string name="sound_setting">サウンド</string>
|
||||||
|
<string name="sound_desc">通知時にサウンドを再生する</string>
|
||||||
|
<string name="test_section">🧪 テスト</string>
|
||||||
|
<string name="test_desc">すべてが正常に機能していることを確認するためにテスト通知を送信する</string>
|
||||||
|
<string name="send_test_notification">テスト通知を送信</string>
|
||||||
|
<string name="add_task_to_test">⚠️ 通知をテストするには少なくとも1つのタスクを追加してください</string>
|
||||||
|
<string name="about_section">ℹ️ アプリについて</string>
|
||||||
|
<string name="app_version">やる気アップ v1.0</string>
|
||||||
|
<string name="app_description">日常のタスクでモチベーションを維持するためのコンパニオン</string>
|
||||||
|
<string name="developed_by">開発者</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">GitHubリポジトリ</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<string name="language_section">🌐 言語</string>
|
||||||
|
<string name="language_desc">アプリケーションの言語を選択してください</string>
|
||||||
|
<string name="language_restart_hint">言語を適用するためにアプリが再起動します</string>
|
||||||
|
|
||||||
|
<string name="notification_default_message">このタスクを完了することを忘れずに!</string>
|
||||||
|
<string name="notification_big_text">📝 タスク:%1$s\n\n🎯 リマインダー:%2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">アクティブなタスクとモチベーション目標を表示</string>
|
||||||
|
<string name="widget_no_tasks">アクティブなタスクがありません。\nやる気アップを開いて追加してください。</string>
|
||||||
|
<string name="widget_tap_to_open">タップして開く →</string>
|
||||||
|
<string name="widget_active">アクティブ</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
86
app/src/main/res/values-ko/strings.xml
Archivo normal
86
app/src/main/res/values-ko/strings.xml
Archivo normal
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">동기부여</string>
|
||||||
|
<string name="notification_channel_name">작업 알림</string>
|
||||||
|
<string name="notification_channel_description">대기 중인 작업을 상기시키는 알림</string>
|
||||||
|
|
||||||
|
<string name="settings_content_desc">설정</string>
|
||||||
|
<string name="add_task_content_desc">작업 추가</string>
|
||||||
|
<string name="empty_state_title">여정을 시작하세요!</string>
|
||||||
|
<string name="empty_state_subtitle">첫 번째 작업과 목표를 추가하여 동기를 유지하세요</string>
|
||||||
|
<string name="goals_label">🎯 목표:</string>
|
||||||
|
<string name="task_paused">⏸️ 일시 중지됨</string>
|
||||||
|
<string name="toggle_active_desc">작업 활성화/일시 중지</string>
|
||||||
|
<string name="delete_task_desc">삭제</string>
|
||||||
|
<string name="delete_task_title">작업 삭제</string>
|
||||||
|
<string name="delete_task_confirm">\'%1$s\'을(를) 정말 삭제하시겠습니까?</string>
|
||||||
|
<string name="delete">삭제</string>
|
||||||
|
<string name="cancel">취소</string>
|
||||||
|
|
||||||
|
<string name="new_task_title">새 작업</string>
|
||||||
|
<string name="back_content_desc">뒤로</string>
|
||||||
|
<string name="what_to_remember">📝 무엇을 기억해야 하나요?</string>
|
||||||
|
<string name="task_title_label">작업 제목</string>
|
||||||
|
<string name="task_title_placeholder">예: 운동하기</string>
|
||||||
|
<string name="what_to_achieve">🎯 무엇을 달성하고 싶나요?</string>
|
||||||
|
<string name="new_goal_label">새 목표</string>
|
||||||
|
<string name="new_goal_placeholder">예: 건강 개선</string>
|
||||||
|
<string name="add_goal_desc">목표 추가</string>
|
||||||
|
<string name="goals_added">추가된 목표:</string>
|
||||||
|
<string name="delete_goal_desc">목표 삭제</string>
|
||||||
|
<string name="daily_reminders_title">🔔 일일 알림</string>
|
||||||
|
<string name="daily_reminders_subtitle">오전 9시부터 오후 9시 사이의 알림 횟수</string>
|
||||||
|
<string name="reminder_singular">회 알림</string>
|
||||||
|
<string name="reminder_plural">회 알림</string>
|
||||||
|
<string name="decrease_desc">줄이기</string>
|
||||||
|
<string name="increase_desc">늘리기</string>
|
||||||
|
<string name="interval_hint">⏱️ 약 %1$s마다 알림 1회</string>
|
||||||
|
<string name="repeat_days_title">📅 며칠마다 반복</string>
|
||||||
|
<string name="repeat_days_subtitle">각 알림 주기 사이의 일수 간격</string>
|
||||||
|
<string name="day_singular">일</string>
|
||||||
|
<string name="day_plural">일</string>
|
||||||
|
<string name="decrease_days_desc">일수 줄이기</string>
|
||||||
|
<string name="increase_days_desc">일수 늘리기</string>
|
||||||
|
<string name="repeat_every_day">🔁 매일 알림</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 %1$d일마다 알림, 겹치지 않게 분산</string>
|
||||||
|
<string name="save_task">작업 저장</string>
|
||||||
|
<string name="edit_task_title">작업 편집</string>
|
||||||
|
<string name="update_task">변경 사항 저장</string>
|
||||||
|
<string name="pause_task_desc">작업 일시 중지</string>
|
||||||
|
<string name="resume_task_desc">작업 재개</string>
|
||||||
|
<string name="long_press_hint">길게 눌러 편집</string>
|
||||||
|
<string name="task_summary_reminders">%1$d회/일 · %2$d일마다</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d회/일 · 매일</string>
|
||||||
|
|
||||||
|
<string name="settings_title">설정</string>
|
||||||
|
<string name="notifications_section">🔔 알림</string>
|
||||||
|
<string name="daily_reminders_setting">일일 알림</string>
|
||||||
|
<string name="daily_reminders_desc">동기 부여 알림 받기</string>
|
||||||
|
<string name="sound_setting">소리</string>
|
||||||
|
<string name="sound_desc">알림과 함께 소리 재생</string>
|
||||||
|
<string name="test_section">🧪 테스트</string>
|
||||||
|
<string name="test_desc">모든 것이 올바르게 작동하는지 확인하기 위해 테스트 알림을 보냅니다</string>
|
||||||
|
<string name="send_test_notification">테스트 알림 보내기</string>
|
||||||
|
<string name="add_task_to_test">⚠️ 알림을 테스트하려면 최소 하나의 작업을 추가하세요</string>
|
||||||
|
<string name="about_section">ℹ️ 앱 정보</string>
|
||||||
|
<string name="app_version">동기부여 v1.0</string>
|
||||||
|
<string name="app_description">일상 작업에서 동기를 유지하는 데 도움을 주는 동반자</string>
|
||||||
|
<string name="developed_by">개발자</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">GitHub 저장소</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<string name="language_section">🌐 언어</string>
|
||||||
|
<string name="language_desc">애플리케이션 언어를 선택하세요</string>
|
||||||
|
<string name="language_restart_hint">언어 적용을 위해 앱이 재시작됩니다</string>
|
||||||
|
|
||||||
|
<string name="notification_default_message">이 작업을 완료하는 것을 잊지 마세요!</string>
|
||||||
|
<string name="notification_big_text">📝 작업: %1$s\n\n🎯 알림: %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">활성 작업과 동기 부여 목표를 표시합니다</string>
|
||||||
|
<string name="widget_no_tasks">활성 작업이 없습니다.\n동기부여 앱을 열어 추가하세요.</string>
|
||||||
|
<string name="widget_tap_to_open">탭하여 열기 →</string>
|
||||||
|
<string name="widget_active">활성</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
86
app/src/main/res/values-pt/strings.xml
Archivo normal
86
app/src/main/res/values-pt/strings.xml
Archivo normal
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Motiva-me</string>
|
||||||
|
<string name="notification_channel_name">Lembretes de Tarefas</string>
|
||||||
|
<string name="notification_channel_description">Notificações para lembrá-lo das suas tarefas pendentes</string>
|
||||||
|
|
||||||
|
<string name="settings_content_desc">Configurações</string>
|
||||||
|
<string name="add_task_content_desc">Adicionar tarefa</string>
|
||||||
|
<string name="empty_state_title">Comece sua jornada!</string>
|
||||||
|
<string name="empty_state_subtitle">Adicione sua primeira tarefa e metas para se manter motivado</string>
|
||||||
|
<string name="goals_label">🎯 Metas:</string>
|
||||||
|
<string name="task_paused">⏸️ Pausada</string>
|
||||||
|
<string name="toggle_active_desc">Ativar/pausar tarefa</string>
|
||||||
|
<string name="delete_task_desc">Excluir</string>
|
||||||
|
<string name="delete_task_title">Excluir tarefa</string>
|
||||||
|
<string name="delete_task_confirm">Tem certeza que deseja excluir \'%1$s\'?</string>
|
||||||
|
<string name="delete">Excluir</string>
|
||||||
|
<string name="cancel">Cancelar</string>
|
||||||
|
|
||||||
|
<string name="new_task_title">Nova Tarefa</string>
|
||||||
|
<string name="back_content_desc">Voltar</string>
|
||||||
|
<string name="what_to_remember">📝 O que você precisa lembrar?</string>
|
||||||
|
<string name="task_title_label">Título da tarefa</string>
|
||||||
|
<string name="task_title_placeholder">Ex: Fazer exercício</string>
|
||||||
|
<string name="what_to_achieve">🎯 O que você espera alcançar?</string>
|
||||||
|
<string name="new_goal_label">Nova meta</string>
|
||||||
|
<string name="new_goal_placeholder">Ex: Melhorar minha saúde</string>
|
||||||
|
<string name="add_goal_desc">Adicionar meta</string>
|
||||||
|
<string name="goals_added">Metas adicionadas:</string>
|
||||||
|
<string name="delete_goal_desc">Excluir meta</string>
|
||||||
|
<string name="daily_reminders_title">🔔 Lembretes diários</string>
|
||||||
|
<string name="daily_reminders_subtitle">Número de lembretes entre 9h00 e 21h00</string>
|
||||||
|
<string name="reminder_singular">lembrete</string>
|
||||||
|
<string name="reminder_plural">lembretes</string>
|
||||||
|
<string name="decrease_desc">Diminuir</string>
|
||||||
|
<string name="increase_desc">Aumentar</string>
|
||||||
|
<string name="interval_hint">⏱️ Um lembrete a cada %1$s aprox.</string>
|
||||||
|
<string name="repeat_days_title">📅 De quantos em quantos dias</string>
|
||||||
|
<string name="repeat_days_subtitle">Intervalo de dias entre cada ciclo de lembretes</string>
|
||||||
|
<string name="day_singular">dia</string>
|
||||||
|
<string name="day_plural">dias</string>
|
||||||
|
<string name="decrease_days_desc">Diminuir dias</string>
|
||||||
|
<string name="increase_days_desc">Aumentar dias</string>
|
||||||
|
<string name="repeat_every_day">🔁 Lembretes todos os dias</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 Lembretes a cada %1$d dias, distribuídos para não coincidir</string>
|
||||||
|
<string name="save_task">Salvar Tarefa</string>
|
||||||
|
<string name="edit_task_title">Editar Tarefa</string>
|
||||||
|
<string name="update_task">Salvar Alterações</string>
|
||||||
|
<string name="pause_task_desc">Pausar tarefa</string>
|
||||||
|
<string name="resume_task_desc">Retomar tarefa</string>
|
||||||
|
<string name="long_press_hint">Pressione longo para editar</string>
|
||||||
|
<string name="task_summary_reminders">%1$d lembrete/dia · a cada %2$d dias</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d lembrete/dia · todos os dias</string>
|
||||||
|
|
||||||
|
<string name="settings_title">Configurações</string>
|
||||||
|
<string name="notifications_section">🔔 Notificações</string>
|
||||||
|
<string name="daily_reminders_setting">Lembretes diários</string>
|
||||||
|
<string name="daily_reminders_desc">Receba notificações para se motivar</string>
|
||||||
|
<string name="sound_setting">Som</string>
|
||||||
|
<string name="sound_desc">Reproduzir som com as notificações</string>
|
||||||
|
<string name="test_section">🧪 Teste</string>
|
||||||
|
<string name="test_desc">Envie uma notificação de teste para verificar se tudo funciona corretamente</string>
|
||||||
|
<string name="send_test_notification">Enviar notificação de teste</string>
|
||||||
|
<string name="add_task_to_test">⚠️ Adicione pelo menos uma tarefa para testar as notificações</string>
|
||||||
|
<string name="about_section">ℹ️ Sobre o app</string>
|
||||||
|
<string name="app_version">Motiva-me v1.0</string>
|
||||||
|
<string name="app_description">Seu companheiro para manter a motivação nas suas tarefas diárias</string>
|
||||||
|
<string name="developed_by">Desenvolvido por</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">Repositório no GitHub</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<string name="language_section">🌐 Idioma</string>
|
||||||
|
<string name="language_desc">Selecione o idioma do aplicativo</string>
|
||||||
|
<string name="language_restart_hint">O app será reiniciado para aplicar o idioma</string>
|
||||||
|
|
||||||
|
<string name="notification_default_message">Lembre-se de completar esta tarefa!</string>
|
||||||
|
<string name="notification_big_text">📝 Tarefa: %1$s\n\n🎯 Lembre-se: %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">Mostra sua tarefa ativa e uma meta motivacional</string>
|
||||||
|
<string name="widget_no_tasks">Sem tarefas ativas.\nAbra Motiva-me para adicionar uma.</string>
|
||||||
|
<string name="widget_tap_to_open">Toque para abrir →</string>
|
||||||
|
<string name="widget_active">ativa</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
89
app/src/main/res/values-zh/strings.xml
Archivo normal
89
app/src/main/res/values-zh/strings.xml
Archivo normal
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">激励我</string>
|
||||||
|
<string name="notification_channel_name">任务提醒</string>
|
||||||
|
<string name="notification_channel_description">提醒您完成待办任务的通知</string>
|
||||||
|
|
||||||
|
<!-- MainScreen -->
|
||||||
|
<string name="settings_content_desc">设置</string>
|
||||||
|
<string name="add_task_content_desc">添加任务</string>
|
||||||
|
<string name="empty_state_title">开始你的旅程!</string>
|
||||||
|
<string name="empty_state_subtitle">添加你的第一个任务和目标,保持动力</string>
|
||||||
|
<string name="goals_label">🎯 目标:</string>
|
||||||
|
<string name="task_paused">⏸️ 已暂停</string>
|
||||||
|
<string name="toggle_active_desc">切换任务状态</string>
|
||||||
|
<string name="delete_task_desc">删除</string>
|
||||||
|
<string name="delete_task_title">删除任务</string>
|
||||||
|
<string name="delete_task_confirm">确定要删除 \'%1$s\' 吗?</string>
|
||||||
|
<string name="delete">删除</string>
|
||||||
|
<string name="cancel">取消</string>
|
||||||
|
|
||||||
|
<!-- AddTaskScreen -->
|
||||||
|
<string name="new_task_title">新任务</string>
|
||||||
|
<string name="back_content_desc">返回</string>
|
||||||
|
<string name="what_to_remember">📝 你需要记住什么?</string>
|
||||||
|
<string name="task_title_label">任务标题</string>
|
||||||
|
<string name="task_title_placeholder">例:锻炼身体</string>
|
||||||
|
<string name="what_to_achieve">🎯 你希望达到什么目标?</string>
|
||||||
|
<string name="new_goal_label">新目标</string>
|
||||||
|
<string name="new_goal_placeholder">例:改善健康</string>
|
||||||
|
<string name="add_goal_desc">添加目标</string>
|
||||||
|
<string name="goals_added">已添加目标:</string>
|
||||||
|
<string name="delete_goal_desc">删除目标</string>
|
||||||
|
<string name="daily_reminders_title">🔔 每日提醒</string>
|
||||||
|
<string name="daily_reminders_subtitle">9:00 至 21:00 之间的提醒次数</string>
|
||||||
|
<string name="reminder_singular">次提醒</string>
|
||||||
|
<string name="reminder_plural">次提醒</string>
|
||||||
|
<string name="decrease_desc">减少</string>
|
||||||
|
<string name="increase_desc">增加</string>
|
||||||
|
<string name="interval_hint">⏱️ 大约每 %1$s 一次提醒</string>
|
||||||
|
<string name="repeat_days_title">📅 每隔几天</string>
|
||||||
|
<string name="repeat_days_subtitle">每个提醒周期之间的天数间隔</string>
|
||||||
|
<string name="day_singular">天</string>
|
||||||
|
<string name="day_plural">天</string>
|
||||||
|
<string name="decrease_days_desc">减少天数</string>
|
||||||
|
<string name="increase_days_desc">增加天数</string>
|
||||||
|
<string name="repeat_every_day">🔁 每天提醒</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 每 %1$d 天提醒,分散安排避免重叠</string>
|
||||||
|
<string name="save_task">保存任务</string>
|
||||||
|
<string name="edit_task_title">编辑任务</string>
|
||||||
|
<string name="update_task">保存更改</string>
|
||||||
|
<string name="pause_task_desc">暂停任务</string>
|
||||||
|
<string name="resume_task_desc">恢复任务</string>
|
||||||
|
<string name="long_press_hint">长按以编辑</string>
|
||||||
|
<string name="task_summary_reminders">每天 %1$d 次提醒 · 每 %2$d 天</string>
|
||||||
|
<string name="task_summary_reminders_daily">每天 %1$d 次提醒 · 每天</string>
|
||||||
|
|
||||||
|
<!-- SettingsScreen -->
|
||||||
|
<string name="settings_title">设置</string>
|
||||||
|
<string name="notifications_section">🔔 通知</string>
|
||||||
|
<string name="daily_reminders_setting">每日提醒</string>
|
||||||
|
<string name="daily_reminders_desc">接收通知以激励自己</string>
|
||||||
|
<string name="sound_setting">声音</string>
|
||||||
|
<string name="sound_desc">通知时播放声音</string>
|
||||||
|
<string name="test_section">🧪 测试</string>
|
||||||
|
<string name="test_desc">发送测试通知以验证一切正常运行</string>
|
||||||
|
<string name="send_test_notification">发送测试通知</string>
|
||||||
|
<string name="add_task_to_test">⚠️ 请至少添加一个任务以测试通知</string>
|
||||||
|
<string name="about_section">ℹ️ 关于应用</string>
|
||||||
|
<string name="app_version">激励我 v1.0</string>
|
||||||
|
<string name="app_description">帮助你在日常任务中保持动力的好伴侣</string>
|
||||||
|
<string name="developed_by">开发者</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">GitHub 仓库</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<!-- Language selector -->
|
||||||
|
<string name="language_section">🌐 语言</string>
|
||||||
|
<string name="language_desc">选择应用程序语言</string>
|
||||||
|
<string name="language_restart_hint">应用将重启以应用语言更改</string>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">显示您的活跃任务和激励目标</string>
|
||||||
|
<string name="widget_no_tasks">没有活跃任务。\n打开激励我来添加一个。</string>
|
||||||
|
<string name="widget_tap_to_open">点击打开 →</string>
|
||||||
|
<string name="widget_active">活跃</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
|
|
||||||
@@ -2,4 +2,88 @@
|
|||||||
<string name="app_name">Motívame</string>
|
<string name="app_name">Motívame</string>
|
||||||
<string name="notification_channel_name">Recordatorios de Tareas</string>
|
<string name="notification_channel_name">Recordatorios de Tareas</string>
|
||||||
<string name="notification_channel_description">Notificaciones para recordarte tus tareas pendientes</string>
|
<string name="notification_channel_description">Notificaciones para recordarte tus tareas pendientes</string>
|
||||||
|
|
||||||
|
<!-- MainScreen -->
|
||||||
|
<string name="settings_content_desc">Configuración</string>
|
||||||
|
<string name="add_task_content_desc">Agregar tarea</string>
|
||||||
|
<string name="empty_state_title">¡Comienza tu viaje!</string>
|
||||||
|
<string name="empty_state_subtitle">Agrega tu primera tarea y metas para mantenerte motivado</string>
|
||||||
|
<string name="goals_label">🎯 Metas:</string>
|
||||||
|
<string name="task_paused">⏸️ Pausada</string>
|
||||||
|
<string name="toggle_active_desc">Activar/pausar tarea</string>
|
||||||
|
<string name="delete_task_desc">Eliminar</string>
|
||||||
|
<string name="delete_task_title">Eliminar tarea</string>
|
||||||
|
<string name="delete_task_confirm">¿Estás seguro de que quieres eliminar \'%1$s\'?</string>
|
||||||
|
<string name="delete">Eliminar</string>
|
||||||
|
<string name="cancel">Cancelar</string>
|
||||||
|
|
||||||
|
<!-- AddTaskScreen -->
|
||||||
|
<string name="new_task_title">Nueva Tarea</string>
|
||||||
|
<string name="back_content_desc">Volver</string>
|
||||||
|
<string name="what_to_remember">📝 ¿Qué debes recordar?</string>
|
||||||
|
<string name="task_title_label">Título de la tarea</string>
|
||||||
|
<string name="task_title_placeholder">Ej: Hacer ejercicio</string>
|
||||||
|
<string name="what_to_achieve">🎯 ¿Qué esperas alcanzar?</string>
|
||||||
|
<string name="new_goal_label">Nueva meta</string>
|
||||||
|
<string name="new_goal_placeholder">Ej: Mejorar mi salud</string>
|
||||||
|
<string name="add_goal_desc">Agregar meta</string>
|
||||||
|
<string name="goals_added">Metas agregadas:</string>
|
||||||
|
<string name="delete_goal_desc">Eliminar meta</string>
|
||||||
|
<string name="daily_reminders_title">🔔 Avisos diarios</string>
|
||||||
|
<string name="daily_reminders_subtitle">Número de recordatorios entre las 9:00 y las 21:00</string>
|
||||||
|
<string name="reminder_singular">aviso</string>
|
||||||
|
<string name="reminder_plural">avisos</string>
|
||||||
|
<string name="decrease_desc">Reducir</string>
|
||||||
|
<string name="increase_desc">Aumentar</string>
|
||||||
|
<string name="interval_hint">⏱️ Un aviso cada %1$s aprox.</string>
|
||||||
|
<string name="repeat_days_title">📅 Cada cuántos días</string>
|
||||||
|
<string name="repeat_days_subtitle">Intervalo de días entre cada ciclo de avisos</string>
|
||||||
|
<string name="day_singular">día</string>
|
||||||
|
<string name="day_plural">días</string>
|
||||||
|
<string name="decrease_days_desc">Reducir días</string>
|
||||||
|
<string name="increase_days_desc">Aumentar días</string>
|
||||||
|
<string name="repeat_every_day">🔁 Avisos todos los días</string>
|
||||||
|
<string name="repeat_every_n_days">🔁 Avisos cada %1$d días, repartidos para no coincidir</string>
|
||||||
|
<string name="save_task">Guardar Tarea</string>
|
||||||
|
<string name="edit_task_title">Editar Tarea</string>
|
||||||
|
<string name="update_task">Guardar Cambios</string>
|
||||||
|
<string name="pause_task_desc">Pausar tarea</string>
|
||||||
|
<string name="resume_task_desc">Reanudar tarea</string>
|
||||||
|
<string name="long_press_hint">Mantén pulsado para editar</string>
|
||||||
|
<string name="task_summary_reminders">%1$d aviso/día · cada %2$d días</string>
|
||||||
|
<string name="task_summary_reminders_daily">%1$d aviso/día · todos los días</string>
|
||||||
|
|
||||||
|
<!-- SettingsScreen -->
|
||||||
|
<string name="settings_title">Configuración</string>
|
||||||
|
<string name="notifications_section">🔔 Notificaciones</string>
|
||||||
|
<string name="daily_reminders_setting">Recordatorios diarios</string>
|
||||||
|
<string name="daily_reminders_desc">Recibe notificaciones para motivarte</string>
|
||||||
|
<string name="sound_setting">Sonido</string>
|
||||||
|
<string name="sound_desc">Reproducir sonido con las notificaciones</string>
|
||||||
|
<string name="test_section">🧪 Prueba</string>
|
||||||
|
<string name="test_desc">Envía una notificación de prueba para verificar que todo funciona correctamente</string>
|
||||||
|
<string name="send_test_notification">Enviar notificación de prueba</string>
|
||||||
|
<string name="add_task_to_test">⚠️ Agrega al menos una tarea para probar las notificaciones</string>
|
||||||
|
<string name="about_section">ℹ️ Sobre la app</string>
|
||||||
|
<string name="app_version">Motívame v1.0</string>
|
||||||
|
<string name="app_description">Tu compañero para mantener la motivación en tus tareas diarias</string>
|
||||||
|
<string name="developed_by">Desarrollado por</string>
|
||||||
|
<string name="developer_url">manalejandro.com</string>
|
||||||
|
<string name="github_label">Repositorio en GitHub</string>
|
||||||
|
<string name="github_url">https://github.com/manalejandro/motivame</string>
|
||||||
|
|
||||||
|
<!-- Language selector -->
|
||||||
|
<string name="language_section">🌐 Idioma</string>
|
||||||
|
<string name="language_desc">Selecciona el idioma de la aplicación</string>
|
||||||
|
<string name="language_restart_hint">La app se reiniciará para aplicar el idioma</string>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<string name="notification_default_message">¡Recuerda completar esta tarea!</string>
|
||||||
|
<string name="notification_big_text">📝 Tarea: %1$s\n\n🎯 Recuerda: %2$s</string>
|
||||||
|
|
||||||
|
<!-- Widget -->
|
||||||
|
<string name="widget_description">Muestra tu tarea activa y una meta motivacional</string>
|
||||||
|
<string name="widget_no_tasks">Sin tareas activas.\nAbre Motívame para añadir una.</string>
|
||||||
|
<string name="widget_tap_to_open">Toca para abrir →</string>
|
||||||
|
<string name="widget_active">activa</string>
|
||||||
</resources>
|
</resources>
|
||||||
13
app/src/main/res/xml/motivame_widget_info.xml
Archivo normal
13
app/src/main/res/xml/motivame_widget_info.xml
Archivo normal
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:minWidth="250dp"
|
||||||
|
android:minHeight="110dp"
|
||||||
|
android:targetCellWidth="3"
|
||||||
|
android:targetCellHeight="2"
|
||||||
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:initialLayout="@layout/widget_motivame"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:widgetCategory="home_screen"
|
||||||
|
android:description="@string/widget_description"
|
||||||
|
android:previewLayout="@layout/widget_motivame" />
|
||||||
|
|
||||||
Referencia en una nueva incidencia
Block a user