2 Commits

Autor SHA1 Mensaje Fecha
ale
15f4d2eead add widget
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m8s
Signed-off-by: ale <ale@manalejandro.com>
2026-03-01 00:02:40 +01:00
ale
95e8d167f2 locales
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m29s
Signed-off-by: ale <ale@manalejandro.com>
2026-02-28 23:46:06 +01:00
Se han modificado 28 ficheros con 2276 adiciones y 353 borrados

345
README.md
Ver fichero

@@ -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 (110) 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:0021: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:0021: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:0021: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>

Ver fichero

@@ -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>

Ver fichero

@@ -1,10 +1,13 @@
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.Task
@@ -14,6 +17,7 @@ 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -21,8 +25,20 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Calendar 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()
@@ -32,7 +48,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
MotivameTheme { MotivameTheme {
MotivameApp(onRescheduleReminders = { scheduleAllReminders() }) MotivameApp(onRescheduleReminders = { enabled -> scheduleAllReminders(enabled) })
} }
} }
} }
@@ -40,19 +56,21 @@ class MainActivity : ComponentActivity() {
/** /**
* Cancela todos los workers anteriores y programa nuevos recordatorios * Cancela todos los workers anteriores y programa nuevos recordatorios
* para cada tarea activa, distribuyendo los avisos entre las 9:00 y las 21:00. * para cada tarea activa, distribuyendo los avisos entre las 9:00 y las 21:00.
* @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() { fun scheduleAllReminders(notificationsEnabled: Boolean? = null) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val repository = TaskRepository(applicationContext) val repository = TaskRepository(applicationContext)
val tasks = repository.tasks.first()
val notificationEnabled = repository.notificationEnabled.first()
// Cancelar todos los workers existentes de recordatorios de tareas // Cancelar todos los workers existentes de recordatorios de tareas
WorkManager.getInstance(applicationContext) WorkManager.getInstance(applicationContext)
.cancelAllWorkByTag("task_reminder") .cancelAllWorkByTag("task_reminder")
if (!notificationEnabled) return@launch val enabled = notificationsEnabled ?: repository.notificationEnabled.first()
if (!enabled) return@launch
val tasks = repository.tasks.first()
tasks.filter { it.isActive }.forEach { task -> tasks.filter { it.isActive }.forEach { task ->
scheduleRemindersForTask(task) scheduleRemindersForTask(task)
} }
@@ -64,32 +82,40 @@ class MainActivity : ComponentActivity() {
val cycleDays = task.repeatEveryDays.coerceIn(1, 30) val cycleDays = task.repeatEveryDays.coerceIn(1, 30)
val workManager = WorkManager.getInstance(applicationContext) val workManager = WorkManager.getInstance(applicationContext)
// Ventana de notificaciones: 9:00 a 21:00 (12 horas = 720 minutos) // Ventana de notificaciones: 9:00 a 21:00 (720 minutos disponibles)
val windowStartHour = 9 val windowStartMinute = 9 * 60 // 540
val windowEndHour = 21 val windowEndMinute = 21 * 60 // 1260
val windowMinutes = (windowEndHour - windowStartHour) * 60 // 720 min val windowSize = windowEndMinute - windowStartMinute // 720
// Distribuir los N avisos a lo largo del ciclo de 'cycleDays' días, // Distribuir los N avisos en días distintos dentro del ciclo.
// repartidos uniformemente para que no coincidan todos el mismo día. // Si reminders <= cycleDays cada aviso va a un día diferente;
// Cada aviso cae en un día y hora distintos dentro del ciclo. // si hay más avisos que días, se reparten de forma ciclica.
val totalSlots = cycleDays // un aviso por día máximo val dayAssignments = (0 until reminders).map { i -> i % cycleDays }
val step = totalSlots.toDouble() / reminders // paso fraccionario entre avisos
for (i in 0 until reminders) { // Generar horas aleatorias únicas (en minutos desde medianoche)
// Día dentro del ciclo (0-based), distribuido uniformemente // Para cada aviso elegimos un minuto al azar dentro de [540, 1260)
val slotIndex = (i * step).toInt() // asegurándonos de que no coincida con ningún otro aviso ya asignado.
val dayOffset = slotIndex % cycleDays val usedMinutes = mutableSetOf<Int>()
val minuteAssignments = mutableListOf<Int>()
// Hora dentro de la ventana: distribuida para que los avisos del mismo día repeat(reminders) {
// no se solapen, o usando posición i para variar la hora entre días var candidate: Int
val offsetMinutes = if (reminders == 1) { var attempts = 0
windowMinutes / 2 // Al mediodía si solo hay 1 aviso do {
} else { candidate = windowStartMinute + Random.nextInt(windowSize)
((windowMinutes * i) / reminders).coerceIn(0, windowMinutes - 30) 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)
} }
val targetHour = windowStartHour + offsetMinutes / 60 for (i in 0 until reminders) {
val targetMinute = offsetMinutes % 60 val dayOffset = dayAssignments[i]
val totalMinutes = minuteAssignments[i]
val targetHour = totalMinutes / 60
val targetMinute = totalMinutes % 60
val delayMs = calculateDelayToTimeWithDayOffset(targetHour, targetMinute, dayOffset) val delayMs = calculateDelayToTimeWithDayOffset(targetHour, targetMinute, dayOffset)
@@ -106,11 +132,6 @@ class MainActivity : ComponentActivity() {
} }
} }
/**
* Calcula el retardo en milisegundos hasta la próxima ocurrencia de la hora indicada.
*/
private fun calculateDelayToTime(hour: Int, minute: Int): Long =
calculateDelayToTimeWithDayOffset(hour, minute, 0)
/** /**
* Calcula el retardo hasta la hora indicada más un desplazamiento de días. * Calcula el retardo hasta la hora indicada más un desplazamiento de días.
@@ -141,25 +162,52 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun MotivameApp(onRescheduleReminders: () -> Unit = {}) { 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 // Registrar callback para reprogramar avisos cuando cambian las tareas
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.onRescheduleReminders = onRescheduleReminders 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" }

Ver fichero

@@ -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)
}
}
}
}

Ver fichero

@@ -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 ->

Ver fichero

@@ -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)
} }
} }

Ver fichero

@@ -1,5 +1,6 @@
package com.manalejandro.motivame.ui.screens package com.manalejandro.motivame.ui.screens
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
@@ -11,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
@@ -20,21 +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(3) } var dailyReminders by remember { mutableStateOf(taskToEdit?.dailyReminders ?: 3) }
var repeatEveryDays by remember { mutableStateOf(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(
@@ -62,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
@@ -71,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 = {
@@ -94,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
@@ -104,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(
@@ -117,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))
} }
} }
) )
@@ -128,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
@@ -173,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
) )
} }
@@ -193,14 +205,14 @@ fun AddTaskScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "🔔 Avisos diarios", text = stringResource(R.string.daily_reminders_title),
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(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = "Número de recordatorios entre las 9:00 y las 21:00", text = stringResource(R.string.daily_reminders_subtitle),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -217,7 +229,7 @@ fun AddTaskScreen(
) { ) {
Icon( Icon(
imageVector = Icons.Default.KeyboardArrowDown, imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = "Reducir", contentDescription = stringResource(R.string.decrease_desc),
tint = if (dailyReminders > 1) MaterialTheme.colorScheme.primary tint = if (dailyReminders > 1) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -231,7 +243,8 @@ fun AddTaskScreen(
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
Text( Text(
text = if (dailyReminders == 1) "aviso" else "avisos", text = if (dailyReminders == 1) stringResource(R.string.reminder_singular)
else stringResource(R.string.reminder_plural),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -243,7 +256,7 @@ fun AddTaskScreen(
) { ) {
Icon( Icon(
imageVector = Icons.Default.KeyboardArrowUp, imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = "Aumentar", contentDescription = stringResource(R.string.increase_desc),
tint = if (dailyReminders < 10) MaterialTheme.colorScheme.primary tint = if (dailyReminders < 10) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -254,7 +267,7 @@ fun AddTaskScreen(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
val intervalMinutes = 720 / (dailyReminders - 1) val intervalMinutes = 720 / (dailyReminders - 1)
Text( Text(
text = "⏱️ Un aviso cada ${formatInterval(intervalMinutes)} aprox.", text = stringResource(R.string.interval_hint, formatInterval(intervalMinutes)),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -274,14 +287,14 @@ fun AddTaskScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "📅 Cada cuántos días", text = stringResource(R.string.repeat_days_title),
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(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = "Intervalo de días entre cada ciclo de avisos", text = stringResource(R.string.repeat_days_subtitle),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -298,7 +311,7 @@ fun AddTaskScreen(
) { ) {
Icon( Icon(
imageVector = Icons.Default.KeyboardArrowDown, imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = "Reducir días", contentDescription = stringResource(R.string.decrease_days_desc),
tint = if (repeatEveryDays > 1) MaterialTheme.colorScheme.primary tint = if (repeatEveryDays > 1) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -312,7 +325,8 @@ fun AddTaskScreen(
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
Text( Text(
text = if (repeatEveryDays == 1) "día" else "días", text = if (repeatEveryDays == 1) stringResource(R.string.day_singular)
else stringResource(R.string.day_plural),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -324,7 +338,7 @@ fun AddTaskScreen(
) { ) {
Icon( Icon(
imageVector = Icons.Default.KeyboardArrowUp, imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = "Aumentar días", contentDescription = stringResource(R.string.increase_days_desc),
tint = if (repeatEveryDays < 30) MaterialTheme.colorScheme.primary tint = if (repeatEveryDays < 30) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -333,8 +347,8 @@ fun AddTaskScreen(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = if (repeatEveryDays == 1) "🔁 Avisos todos los días" text = if (repeatEveryDays == 1) stringResource(R.string.repeat_every_day)
else "🔁 Avisos cada $repeatEveryDays días, repartidos para no coincidir", else stringResource(R.string.repeat_every_n_days, repeatEveryDays),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -347,6 +361,17 @@ fun AddTaskScreen(
Button( Button(
onClick = { onClick = {
if (taskTitle.isNotBlank()) { if (taskTitle.isNotBlank()) {
val existing = taskToEdit
if (existing != null) {
viewModel.updateTask(
existing.copy(
title = taskTitle.trim(),
goals = goals,
dailyReminders = dailyReminders,
repeatEveryDays = repeatEveryDays
)
)
} else {
val newTask = Task( val newTask = Task(
title = taskTitle.trim(), title = taskTitle.trim(),
goals = goals, goals = goals,
@@ -355,6 +380,7 @@ fun AddTaskScreen(
repeatEveryDays = repeatEveryDays repeatEveryDays = repeatEveryDays
) )
viewModel.addTask(newTask) viewModel.addTask(newTask)
}
onNavigateBack() onNavigateBack()
} }
}, },
@@ -367,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
) )

Ver fichero

@@ -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))
Column {
Text( Text(
text = task.title, text = task.title,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface 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
) )
} }
} }

Ver fichero

@@ -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)
}
)
}
} }
} }
} }
} }
} }
} }

Ver fichero

@@ -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,8 +24,12 @@ 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()
/** Callback que se invoca tras cualquier cambio en las tareas para reprogramar avisos */ private val _language = MutableStateFlow("es")
var onRescheduleReminders: (() -> Unit)? = null 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()
@@ -50,26 +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() 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() 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() onRescheduleReminders?.invoke(_notificationEnabled.value)
MotivameWidget.requestUpdate(getApplication())
} }
} }
@@ -77,7 +90,8 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch { viewModelScope.launch {
repository.setNotificationEnabled(enabled) repository.setNotificationEnabled(enabled)
_notificationEnabled.value = enabled _notificationEnabled.value = enabled
onRescheduleReminders?.invoke() // Pasamos `enabled` directamente para no releer DataStore
onRescheduleReminders?.invoke(enabled)
} }
} }
@@ -87,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
}
}

Ver fichero

@@ -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)
}
}

Ver fichero

@@ -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
}
)
}
}
}

Ver fichero

@@ -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(
@@ -38,6 +39,9 @@ class DailyReminderWorker(
notificationHelper.sendTaskReminder(it, soundEnabled) notificationHelper.sendTaskReminder(it, soundEnabled)
} }
// Refrescar el widget con la meta actualizada
MotivameWidget.requestUpdate(applicationContext)
return Result.success() return Result.success()
} }
} }

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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>

Ver fichero

@@ -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" />