2 Commits

Autor SHA1 Mensaje Fecha
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
ale
e7606df296 more entropy
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 7m16s
Signed-off-by: ale <ale@manalejandro.com>
2026-02-28 09:04:32 +01:00
Se han modificado 21 ficheros con 1639 adiciones y 334 borrados

343
README.md
Ver fichero

@@ -1,207 +1,208 @@
# 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
- **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
👉 [github.com/manalejandro/motivame](https://github.com/manalejandro/motivame)
### Componentes Principales
---
#### 1. Pantalla Principal
- 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
## ✨ Características
#### 2. Agregar Tareas
- Campo de título de tarea
- Agregar múltiples metas personalizadas
- Validación de campos
- Interfaz intuitiva con iconos descriptivos
| Función | Descripción |
|---|---|
| 📝 **Gestión de tareas** | Crea, edita (pulsación larga) y elimina tareas |
| 🎯 **Metas por tarea** | Asocia múltiples objetivos a cada tarea |
| ⏯️ **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 |
#### 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
- Persiste incluso después de reiniciar el dispositivo
- Optimizado para el consumo de batería
- No requiere conexión a Internet
1. **Crea una tarea** — ponle título y añade tus metas (el «por qué»).
2. **Configura la frecuencia** — número de avisos diarios y cada cuántos días se repite el ciclo.
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.
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
// Core Android
androidx.core:core-ktx:1.10.1
androidx.lifecycle:lifecycle-runtime-ktx:2.6.1
androidx.activity:activity-compose:1.8.0
## 🏗️ Arquitectura y tecnología
// Compose
androidx.compose:compose-bom:2024.09.00
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
```
MVVM · Jetpack Compose · WorkManager · DataStore · Kotlin Coroutines
```
## 🔧 Requisitos
- **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
### Estructura del proyecto
```
app/src/main/java/com/manalejandro/motivame/
├── data/
│ ├── Task.kt # Modelo de datos
│ └── TaskRepository.kt # Repositorio de persistencia
│ └── TaskRepository.kt # Persistencia con DataStore
├── notifications/
│ └── NotificationHelper.kt # Gestión de notificaciones
│ └── NotificationHelper.kt # Envío de notificaciones (Ringtone independiente del canal)
├── ui/
│ ├── screens/
│ │ ├── MainScreen.kt # Pantalla principal
│ │ ├── AddTaskScreen.kt # Pantalla agregar tarea
│ │ └── SettingsScreen.kt # Pantalla configuración
│ │ ├── MainScreen.kt # Lista de tareas
│ │ ├── AddTaskScreen.kt # Crear / editar tarea
│ │ └── SettingsScreen.kt # Configuración (idioma, notificaciones, sonido)
│ ├── theme/
│ │ ├── Color.kt # Definición de colores
│ │ ├── Theme.kt # Tema de la aplicación
│ │ └── Type.kt # Tipografía
│ │ ├── Color.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── 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/
│ └── DailyReminderWorker.kt # Worker para recordatorios
── MainActivity.kt # Actividad principal
│ └── DailyReminderWorker.kt # WorkManager: ejecuta recordatorios programados
── 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
- [ ] 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
android:allowBackup="true"
android:name=".MotivameApplication"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"

Ver fichero

@@ -1,86 +1,213 @@
package com.manalejandro.motivame
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.work.*
import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.data.TaskRepository
import com.manalejandro.motivame.ui.screens.AddTaskScreen
import com.manalejandro.motivame.ui.screens.MainScreen
import com.manalejandro.motivame.ui.screens.SettingsScreen
import com.manalejandro.motivame.ui.theme.MotivameTheme
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
import com.manalejandro.motivame.util.LocaleHelper
import com.manalejandro.motivame.worker.DailyReminderWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.concurrent.TimeUnit
import kotlin.random.Random
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?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// Configurar recordatorios diarios
setupDailyReminders()
// Programar recordatorios para todas las tareas activas
scheduleAllReminders()
setContent {
MotivameTheme {
MotivameApp()
MotivameApp(onRescheduleReminders = { enabled -> scheduleAllReminders(enabled) })
}
}
}
private fun setupDailyReminders() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
/**
* Cancela todos los workers anteriores y programa nuevos recordatorios
* 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(notificationsEnabled: Boolean? = null) {
CoroutineScope(Dispatchers.IO).launch {
val repository = TaskRepository(applicationContext)
// Cancelar todos los workers existentes de recordatorios de tareas
WorkManager.getInstance(applicationContext)
.cancelAllWorkByTag("task_reminder")
val enabled = notificationsEnabled ?: repository.notificationEnabled.first()
if (!enabled) return@launch
val tasks = repository.tasks.first()
tasks.filter { it.isActive }.forEach { task ->
scheduleRemindersForTask(task)
}
}
}
private fun scheduleRemindersForTask(task: Task) {
val reminders = task.dailyReminders.coerceIn(1, 10)
val cycleDays = task.repeatEveryDays.coerceIn(1, 30)
val workManager = WorkManager.getInstance(applicationContext)
// Ventana de notificaciones: 9:00 a 21:00 (720 minutos disponibles)
val windowStartMinute = 9 * 60 // 540
val windowEndMinute = 21 * 60 // 1260
val windowSize = windowEndMinute - windowStartMinute // 720
// Distribuir los N avisos en días distintos dentro del ciclo.
// Si reminders <= cycleDays cada aviso va a un día diferente;
// si hay más avisos que días, se reparten de forma ciclica.
val dayAssignments = (0 until reminders).map { i -> i % cycleDays }
// Generar horas aleatorias únicas (en minutos desde medianoche)
// Para cada aviso elegimos un minuto al azar dentro de [540, 1260)
// asegurándonos de que no coincida con ningún otro aviso ya asignado.
val usedMinutes = mutableSetOf<Int>()
val minuteAssignments = mutableListOf<Int>()
repeat(reminders) {
var candidate: Int
var attempts = 0
do {
candidate = windowStartMinute + Random.nextInt(windowSize)
attempts++
// Tras muchos intentos (espacio muy saturado) relajamos la condición
// exigiendo sólo minutos distintos en el mismo día
} while (usedMinutes.contains(candidate) && attempts < windowSize)
usedMinutes.add(candidate)
minuteAssignments.add(candidate)
}
for (i in 0 until reminders) {
val dayOffset = dayAssignments[i]
val totalMinutes = minuteAssignments[i]
val targetHour = totalMinutes / 60
val targetMinute = totalMinutes % 60
val delayMs = calculateDelayToTimeWithDayOffset(targetHour, targetMinute, dayOffset)
val inputData = workDataOf(DailyReminderWorker.KEY_TASK_ID to task.id)
val workRequest = OneTimeWorkRequestBuilder<DailyReminderWorker>()
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.addTag("task_reminder")
.addTag("task_${task.id}")
.build()
val dailyWorkRequest = PeriodicWorkRequestBuilder<DailyReminderWorker>(
1, TimeUnit.DAYS
)
.setConstraints(constraints)
.setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork(
"daily_reminder",
ExistingPeriodicWorkPolicy.KEEP,
dailyWorkRequest
)
workManager.enqueue(workRequest)
}
}
private fun calculateInitialDelay(): Long {
val currentTime = System.currentTimeMillis()
val calendar = java.util.Calendar.getInstance().apply {
timeInMillis = currentTime
set(java.util.Calendar.HOUR_OF_DAY, 9) // 9 AM
set(java.util.Calendar.MINUTE, 0)
set(java.util.Calendar.SECOND, 0)
/**
* Calcula el retardo hasta la hora indicada más un desplazamiento de días.
* Si la hora ya pasó hoy, se mueve al día siguiente antes de aplicar el offset.
*/
private fun calculateDelayToTimeWithDayOffset(hour: Int, minute: Int, dayOffset: Int): Long {
val now = System.currentTimeMillis()
val calendar = Calendar.getInstance().apply {
timeInMillis = now
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
if (calendar.timeInMillis <= currentTime) {
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
// Si ya pasó esa hora hoy, mover a mañana antes de aplicar el offset
if (calendar.timeInMillis <= now) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
return calendar.timeInMillis - currentTime
// Aplicar el offset de días adicionales
if (dayOffset > 0) {
calendar.add(Calendar.DAY_OF_YEAR, dayOffset)
}
return calendar.timeInMillis - now
}
}
@Composable
fun MotivameApp() {
fun MotivameApp(onRescheduleReminders: (Boolean) -> Unit = {}) {
val viewModel: TaskViewModel = viewModel()
val context = LocalContext.current
var currentScreen by remember { mutableStateOf("main") }
var taskToEdit by remember { mutableStateOf<Task?>(null) }
// Registrar callback para reprogramar avisos cuando cambian las tareas
LaunchedEffect(viewModel) {
viewModel.onRescheduleReminders = { enabled -> onRescheduleReminders(enabled) }
}
// Interceptar el botón físico Atrás del sistema
BackHandler(enabled = currentScreen != "main") {
taskToEdit = null
currentScreen = "main"
}
// En la pantalla principal, minimizar en lugar de cerrar
BackHandler(enabled = currentScreen == "main") {
(context as? ComponentActivity)?.moveTaskToBack(true)
}
when (currentScreen) {
"main" -> MainScreen(
viewModel = viewModel,
onNavigateToAddTask = { currentScreen = "add_task" },
onNavigateToSettings = { currentScreen = "settings" }
onNavigateToAddTask = {
taskToEdit = null
currentScreen = "add_task"
},
onNavigateToSettings = { currentScreen = "settings" },
onEditTask = { task ->
taskToEdit = task
currentScreen = "edit_task"
}
)
"add_task" -> AddTaskScreen(
viewModel = viewModel,
onNavigateBack = { currentScreen = "main" }
)
"edit_task" -> AddTaskScreen(
viewModel = viewModel,
onNavigateBack = {
taskToEdit = null
currentScreen = "main"
},
taskToEdit = taskToEdit
)
"settings" -> SettingsScreen(
viewModel = viewModel,
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

@@ -7,6 +7,8 @@ data class Task(
val title: String,
val goals: List<String>,
val isActive: Boolean = true,
val createdAt: Long = System.currentTimeMillis()
val createdAt: Long = System.currentTimeMillis(),
val dailyReminders: Int = 3, // Número de avisos por día (entre 9:00 y 21:00)
val repeatEveryDays: Int = 3 // Cada cuántos días se repite el ciclo de avisos
)

Ver fichero

@@ -19,6 +19,7 @@ class TaskRepository(private val context: Context) {
private val TASKS_KEY = stringPreferencesKey("tasks")
private val NOTIFICATION_ENABLED_KEY = stringPreferencesKey("notification_enabled")
private val SOUND_ENABLED_KEY = stringPreferencesKey("sound_enabled")
private val LANGUAGE_KEY = stringPreferencesKey("language")
val DEFAULT_TASKS = listOf(
Task(
@@ -68,6 +69,11 @@ class TaskRepository(private val context: Context) {
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>) {
context.dataStore.edit { preferences ->
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 {
val jsonArray = JSONArray()
tasks.forEach { task ->
@@ -119,6 +131,8 @@ class TaskRepository(private val context: Context) {
put("goals", JSONArray(task.goals))
put("isActive", task.isActive)
put("createdAt", task.createdAt)
put("dailyReminders", task.dailyReminders)
put("repeatEveryDays", task.repeatEveryDays)
}
jsonArray.put(jsonObject)
}
@@ -140,7 +154,9 @@ class TaskRepository(private val context: Context) {
title = jsonObject.getString("title"),
goals = goals,
isActive = jsonObject.getBoolean("isActive"),
createdAt = jsonObject.getLong("createdAt")
createdAt = jsonObject.getLong("createdAt"),
dailyReminders = if (jsonObject.has("dailyReminders")) jsonObject.getInt("dailyReminders") else 3,
repeatEveryDays = if (jsonObject.has("repeatEveryDays")) jsonObject.getInt("repeatEveryDays") else 3
)
}
} catch (e: Exception) {

Ver fichero

@@ -1,12 +1,10 @@
package com.manalejandro.motivame.notifications
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.Ringtone
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.manalejandro.motivame.MainActivity
@@ -17,77 +15,57 @@ import kotlin.random.Random
class NotificationHelper(private val context: Context) {
companion object {
private const val CHANNEL_ID = "motivame_channel"
private const val CHANNEL_NAME = "Recordatorios de Tareas"
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)
}
const val CHANNEL_ID_SOUND = "motivame_channel_sound"
const val CHANNEL_ID_NO_SOUND = "motivame_channel_silent"
}
fun sendTaskReminder(task: Task, withSound: Boolean = true) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
context, 0, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val motivationalMessage = if (task.goals.isNotEmpty()) {
task.goals.random()
} else {
"¡Recuerda completar esta tarea!"
}
val motivationalMessage = if (task.goals.isNotEmpty()) task.goals.random()
else context.getString(R.string.notification_default_message)
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)
.setContentTitle("${task.title}")
.setContentText(motivationalMessage)
.setStyle(NotificationCompat.BigTextStyle().bigText(
"📝 Tarea: ${task.title}\n\n🎯 Recuerda: $motivationalMessage"
))
.setStyle(
NotificationCompat.BigTextStyle().bigText(
context.getString(R.string.notification_big_text, task.title, motivationalMessage)
)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setVibrate(longArrayOf(0, 500, 250, 500))
if (withSound) {
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
notificationBuilder.setSound(defaultSoundUri)
}
.setSilent(true) // siempre silenciosa a nivel canal
try {
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(Random.nextInt(), notificationBuilder.build())
} catch (e: SecurityException) {
// El usuario no ha concedido permisos de notificación
NotificationManagerCompat.from(context)
.notify(Random.nextInt(), notificationBuilder.build())
} catch (_: SecurityException) { }
// 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) {
if (tasks.isEmpty()) return
val activeTask = tasks.firstOrNull { it.isActive } ?: return
sendTaskReminder(activeTask, withSound)
}
}

Ver fichero

@@ -1,6 +1,6 @@
package com.manalejandro.motivame.ui.screens
import androidx.compose.foundation.background
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
@@ -12,8 +12,10 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.manalejandro.motivame.R
import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
@@ -21,19 +23,30 @@ import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
@Composable
fun AddTaskScreen(
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 goals by remember { mutableStateOf(listOf<String>()) }
var goals by remember { mutableStateOf(taskToEdit?.goals ?: listOf()) }
var dailyReminders by remember { mutableStateOf(taskToEdit?.dailyReminders ?: 3) }
var repeatEveryDays by remember { mutableStateOf(taskToEdit?.repeatEveryDays ?: 3) }
BackHandler { onNavigateBack() }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Nueva Tarea") },
title = {
Text(
if (isEditing) stringResource(R.string.edit_task_title)
else stringResource(R.string.new_task_title)
)
},
navigationIcon = {
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(
@@ -61,7 +74,7 @@ fun AddTaskScreen(
.padding(16.dp)
) {
Text(
text = "📝 ¿Qué debes recordar?",
text = stringResource(R.string.what_to_remember),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
@@ -70,8 +83,8 @@ fun AddTaskScreen(
OutlinedTextField(
value = taskTitle,
onValueChange = { taskTitle = it },
label = { Text("Título de la tarea") },
placeholder = { Text("Ej: Hacer ejercicio") },
label = { Text(stringResource(R.string.task_title_label)) },
placeholder = { Text(stringResource(R.string.task_title_placeholder)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
leadingIcon = {
@@ -93,7 +106,7 @@ fun AddTaskScreen(
.padding(16.dp)
) {
Text(
text = "🎯 ¿Qué esperas alcanzar?",
text = stringResource(R.string.what_to_achieve),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
@@ -103,8 +116,8 @@ fun AddTaskScreen(
OutlinedTextField(
value = currentGoal,
onValueChange = { currentGoal = it },
label = { Text("Nueva meta") },
placeholder = { Text("Ej: Mejorar mi salud") },
label = { Text(stringResource(R.string.new_goal_label)) },
placeholder = { Text(stringResource(R.string.new_goal_placeholder)) },
modifier = Modifier.fillMaxWidth(),
trailingIcon = {
IconButton(
@@ -116,7 +129,7 @@ fun AddTaskScreen(
},
enabled = currentGoal.isNotBlank()
) {
Icon(Icons.Default.Add, contentDescription = "Agregar meta")
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_goal_desc))
}
}
)
@@ -127,7 +140,7 @@ fun AddTaskScreen(
if (goals.isNotEmpty()) {
item {
Text(
text = "Metas agregadas:",
text = stringResource(R.string.goals_added),
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -172,7 +185,7 @@ fun AddTaskScreen(
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Eliminar meta",
contentDescription = stringResource(R.string.delete_goal_desc),
tint = MaterialTheme.colorScheme.error
)
}
@@ -181,17 +194,193 @@ fun AddTaskScreen(
}
}
item {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = stringResource(R.string.daily_reminders_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.daily_reminders_subtitle),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { if (dailyReminders > 1) dailyReminders-- },
enabled = dailyReminders > 1
) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = stringResource(R.string.decrease_desc),
tint = if (dailyReminders > 1) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "$dailyReminders",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Text(
text = if (dailyReminders == 1) stringResource(R.string.reminder_singular)
else stringResource(R.string.reminder_plural),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(
onClick = { if (dailyReminders < 10) dailyReminders++ },
enabled = dailyReminders < 10
) {
Icon(
imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = stringResource(R.string.increase_desc),
tint = if (dailyReminders < 10) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
if (dailyReminders > 1) {
Spacer(modifier = Modifier.height(8.dp))
val intervalMinutes = 720 / (dailyReminders - 1)
Text(
text = stringResource(R.string.interval_hint, formatInterval(intervalMinutes)),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
item {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = stringResource(R.string.repeat_days_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.repeat_days_subtitle),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { if (repeatEveryDays > 1) repeatEveryDays-- },
enabled = repeatEveryDays > 1
) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = stringResource(R.string.decrease_days_desc),
tint = if (repeatEveryDays > 1) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "$repeatEveryDays",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Text(
text = if (repeatEveryDays == 1) stringResource(R.string.day_singular)
else stringResource(R.string.day_plural),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(
onClick = { if (repeatEveryDays < 30) repeatEveryDays++ },
enabled = repeatEveryDays < 30
) {
Icon(
imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = stringResource(R.string.increase_days_desc),
tint = if (repeatEveryDays < 30) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = if (repeatEveryDays == 1) stringResource(R.string.repeat_every_day)
else stringResource(R.string.repeat_every_n_days, repeatEveryDays),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
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(
title = taskTitle.trim(),
goals = goals,
isActive = true
isActive = true,
dailyReminders = dailyReminders,
repeatEveryDays = repeatEveryDays
)
viewModel.addTask(newTask)
}
onNavigateBack()
}
},
@@ -204,7 +393,8 @@ fun AddTaskScreen(
Icon(Icons.Default.Check, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Guardar Tarea",
text = if (isEditing) stringResource(R.string.update_task)
else stringResource(R.string.save_task),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
@@ -214,6 +404,12 @@ fun AddTaskScreen(
}
}
private fun formatInterval(minutes: Int): String {
return if (minutes >= 60) {
val h = minutes / 60
val m = minutes % 60
if (m == 0) "${h}h" else "${h}h ${m}min"
} else {
"${minutes}min"
}
}

Ver fichero

@@ -1,6 +1,8 @@
package com.manalejandro.motivame.ui.screens
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -13,8 +15,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.manalejandro.motivame.R
import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
@@ -23,7 +27,8 @@ import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
fun MainScreen(
viewModel: TaskViewModel,
onNavigateToAddTask: () -> Unit,
onNavigateToSettings: () -> Unit
onNavigateToSettings: () -> Unit,
onEditTask: (Task) -> Unit = {}
) {
val tasks by viewModel.tasks.collectAsState()
@@ -32,14 +37,14 @@ fun MainScreen(
TopAppBar(
title = {
Text(
"Motívame",
stringResource(R.string.app_name),
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
},
actions = {
IconButton(onClick = onNavigateToSettings) {
Icon(Icons.Default.Settings, contentDescription = "Configuración")
Icon(Icons.Default.Settings, contentDescription = stringResource(R.string.settings_content_desc))
}
},
colors = TopAppBarDefaults.topAppBarColors(
@@ -53,7 +58,7 @@ fun MainScreen(
onClick = onNavigateToAddTask,
containerColor = MaterialTheme.colorScheme.primary
) {
Icon(Icons.Default.Add, contentDescription = "Agregar tarea")
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_task_content_desc))
}
}
) { paddingValues ->
@@ -71,7 +76,8 @@ fun MainScreen(
TaskCard(
task = task,
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
fun TaskCard(
task: Task,
onToggleActive: () -> Unit,
onDelete: () -> Unit
onDelete: () -> Unit,
onEdit: () -> Unit = {}
) {
var showDeleteDialog by remember { mutableStateOf(false) }
Card(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onClick = {},
onLongClick = onEdit
),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
shape = RoundedCornerShape(16.dp)
) {
@@ -98,8 +111,10 @@ fun TaskCard(
.background(
brush = Brush.verticalGradient(
colors = listOf(
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.surface
if (task.isActive) MaterialTheme.colorScheme.surfaceVariant
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(
imageVector = Icons.Default.Star,
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)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = task.title,
style = MaterialTheme.typography.titleLarge,
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 {
// Botón pausa/reanudar
IconButton(onClick = onToggleActive) {
Icon(
imageVector = if (task.isActive) Icons.Default.Check else Icons.Default.Close,
contentDescription = "Toggle activo",
tint = if (task.isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
imageVector = if (task.isActive) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (task.isActive) stringResource(R.string.pause_task_desc)
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 }) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Eliminar",
contentDescription = stringResource(R.string.delete_task_desc),
tint = MaterialTheme.colorScheme.error
)
}
@@ -150,10 +186,11 @@ fun TaskCard(
if (task.goals.isNotEmpty()) {
Spacer(modifier = Modifier.height(12.dp))
Text(
text = "🎯 Metas:",
text = stringResource(R.string.goals_label),
style = MaterialTheme.typography.titleSmall,
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))
@@ -165,18 +202,48 @@ fun TaskCard(
Text(
text = "",
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)
)
Text(
text = goal,
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) {
Spacer(modifier = Modifier.height(8.dp))
Box(
@@ -186,7 +253,7 @@ fun TaskCard(
.padding(horizontal = 12.dp, vertical = 6.dp)
) {
Text(
text = "⏸️ Pausada",
text = stringResource(R.string.task_paused),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onErrorContainer
)
@@ -198,8 +265,8 @@ fun TaskCard(
if (showDeleteDialog) {
AlertDialog(
onDismissRequest = { showDeleteDialog = false },
title = { Text("Eliminar tarea") },
text = { Text("¿Estás seguro de que quieres eliminar '${task.title}'?") },
title = { Text(stringResource(R.string.delete_task_title)) },
text = { Text(stringResource(R.string.delete_task_confirm, task.title)) },
confirmButton = {
TextButton(
onClick = {
@@ -207,12 +274,12 @@ fun TaskCard(
showDeleteDialog = false
}
) {
Text("Eliminar", color = MaterialTheme.colorScheme.error)
Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error)
}
},
dismissButton = {
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))
Text(
text = "¡Comienza tu viaje!",
text = stringResource(R.string.empty_state_title),
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Agrega tu primera tarea y metas para mantenerte motivado",
text = stringResource(R.string.empty_state_subtitle),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}

Ver fichero

@@ -1,10 +1,15 @@
package com.manalejandro.motivame.ui.screens
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
@@ -15,12 +20,15 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
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.ui.viewmodel.TaskViewModel
import com.manalejandro.motivame.util.LocaleHelper
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@@ -34,6 +42,9 @@ fun SettingsScreen(
val notificationEnabled by viewModel.notificationEnabled.collectAsState()
val soundEnabled by viewModel.soundEnabled.collectAsState()
val tasks by viewModel.tasks.collectAsState()
val currentLanguage by viewModel.language.collectAsState()
BackHandler { onNavigateBack() }
var hasNotificationPermission by remember {
mutableStateOf(
@@ -54,13 +65,15 @@ fun SettingsScreen(
hasNotificationPermission = isGranted
}
var languageMenuExpanded by remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Configuración") },
title = { Text(stringResource(R.string.settings_title)) },
navigationIcon = {
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(
@@ -77,6 +90,7 @@ fun SettingsScreen(
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// --- Idioma ---
item {
Card(
modifier = Modifier.fillMaxWidth(),
@@ -88,7 +102,87 @@ fun SettingsScreen(
.padding(16.dp)
) {
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,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
@@ -102,12 +196,12 @@ fun SettingsScreen(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Recordatorios diarios",
text = stringResource(R.string.daily_reminders_setting),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium
)
Text(
text = "Recibe notificaciones para motivarte",
text = stringResource(R.string.daily_reminders_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -135,12 +229,12 @@ fun SettingsScreen(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Sonido",
text = stringResource(R.string.sound_setting),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium
)
Text(
text = "Reproducir sonido con las notificaciones",
text = stringResource(R.string.sound_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -154,6 +248,7 @@ fun SettingsScreen(
}
}
// --- Prueba ---
item {
Card(
modifier = Modifier.fillMaxWidth(),
@@ -165,14 +260,14 @@ fun SettingsScreen(
.padding(16.dp)
) {
Text(
text = "🧪 Prueba",
text = stringResource(R.string.test_section),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
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,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -194,13 +289,13 @@ fun SettingsScreen(
) {
Icon(Icons.Default.Notifications, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text("Enviar notificación de prueba")
Text(stringResource(R.string.send_test_notification))
}
if (tasks.isEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "⚠️ Agrega al menos una tarea para probar las notificaciones",
text = stringResource(R.string.add_task_to_test),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
@@ -209,6 +304,7 @@ fun SettingsScreen(
}
}
// --- Sobre la app ---
item {
Card(
modifier = Modifier.fillMaxWidth(),
@@ -223,29 +319,86 @@ fun SettingsScreen(
.padding(16.dp)
) {
Text(
text = " Sobre la app",
text = stringResource(R.string.about_section),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onTertiaryContainer
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Motívame v1.0",
text = stringResource(R.string.app_version),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onTertiaryContainer
)
Text(
text = "Tu compañero para mantener la motivación en tus tareas diarias",
text = stringResource(R.string.app_description),
style = MaterialTheme.typography.bodySmall,
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

@@ -23,6 +23,13 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val _soundEnabled = MutableStateFlow(true)
val soundEnabled: StateFlow<Boolean> = _soundEnabled.asStateFlow()
private val _language = MutableStateFlow("es")
val language: StateFlow<String> = _language.asStateFlow()
/** Callback que se invoca tras cualquier cambio en las tareas para reprogramar avisos.
* Recibe el valor actual de notificationEnabled para evitar condiciones de carrera con DataStore. */
var onRescheduleReminders: ((notificationsEnabled: Boolean) -> Unit)? = null
init {
loadTasks()
loadSettings()
@@ -47,23 +54,31 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
_soundEnabled.value = enabled
}
}
viewModelScope.launch {
repository.language.collect { lang ->
_language.value = lang
}
}
}
fun addTask(task: Task) {
viewModelScope.launch {
repository.addTask(task)
onRescheduleReminders?.invoke(_notificationEnabled.value)
}
}
fun updateTask(task: Task) {
viewModelScope.launch {
repository.updateTask(task)
onRescheduleReminders?.invoke(_notificationEnabled.value)
}
}
fun deleteTask(taskId: String) {
viewModelScope.launch {
repository.deleteTask(taskId)
onRescheduleReminders?.invoke(_notificationEnabled.value)
}
}
@@ -71,6 +86,8 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch {
repository.setNotificationEnabled(enabled)
_notificationEnabled.value = enabled
// Pasamos `enabled` directamente para no releer DataStore
onRescheduleReminders?.invoke(enabled)
}
}
@@ -80,5 +97,9 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
_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

@@ -12,16 +12,30 @@ class DailyReminderWorker(
params: WorkerParameters
) : CoroutineWorker(context, params) {
companion object {
const val KEY_TASK_ID = "task_id"
}
override suspend fun doWork(): Result {
val repository = TaskRepository(applicationContext)
val notificationHelper = NotificationHelper(applicationContext)
val tasks = repository.tasks.first()
val notificationEnabled = repository.notificationEnabled.first()
val soundEnabled = repository.soundEnabled.first()
if (!notificationEnabled) return Result.success()
if (notificationEnabled && tasks.isNotEmpty()) {
notificationHelper.sendMotivationalReminder(tasks, soundEnabled)
val soundEnabled = repository.soundEnabled.first()
val taskId = inputData.getString(KEY_TASK_ID)
val tasks = repository.tasks.first()
val taskToNotify = if (taskId != null) {
tasks.firstOrNull { it.id == taskId && it.isActive }
} else {
tasks.firstOrNull { it.isActive }
}
taskToNotify?.let {
notificationHelper.sendTaskReminder(it, soundEnabled)
}
return Result.success()

Ver fichero

@@ -0,0 +1,80 @@
<?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>
</resources>

Ver fichero

@@ -0,0 +1,86 @@
<?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>
</resources>

Ver fichero

@@ -0,0 +1,80 @@
<?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>
</resources>

Ver fichero

@@ -0,0 +1,80 @@
<?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>
</resources>

Ver fichero

@@ -0,0 +1,80 @@
<?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>
</resources>

Ver fichero

@@ -0,0 +1,80 @@
<?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>
</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>
<!-- 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 -->
<string name="notification_default_message">记得完成这个任务!</string>
<string name="notification_big_text">📝 任务:%1$s\n\n🎯 提醒:%2$s</string>
</resources>

Ver fichero

@@ -2,4 +2,82 @@
<string name="app_name">Motívame</string>
<string name="notification_channel_name">Recordatorios de Tareas</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>
</resources>