3 Commits

Autor SHA1 Mensaje Fecha
ale
16b7892e0a fix timezone
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 59s
Signed-off-by: ale <ale@manalejandro.com>
2026-03-11 01:02:35 +01:00
ale
b3694fae56 fix lint
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m44s
Signed-off-by: ale <ale@manalejandro.com>
2026-03-01 01:05:24 +01:00
ale
15f4d2eead add widget
Algunas comprobaciones han fallado
Build & Publish APK Release / build (push) Failing after 6m8s
Signed-off-by: ale <ale@manalejandro.com>
2026-03-01 00:02:40 +01:00
Se han modificado 32 ficheros con 1184 adiciones y 300 borrados

Ver fichero

@@ -34,6 +34,7 @@
| 🔊 **Sonido configurable** | Activa o desactiva el sonido de las notificaciones | | 🔊 **Sonido configurable** | Activa o desactiva el sonido de las notificaciones |
| 🌐 **Multiidioma** | 8 idiomas: Español · English · 中文 · Français · Deutsch · Português · 日本語 · 한국어 | | 🌐 **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 | | 🎨 **Material Design 3** | Interfaz moderna con gradientes, colores vibrantes y soporte edge-to-edge |
| 🟣 **Widget** | Widget de escritorio que muestra la tarea activa y una meta aleatoria |
--- ---
@@ -184,6 +185,7 @@ El idioma se selecciona desde **Configuración → Idioma** y se aplica instant
- [x] Ciclo de días configurable - [x] Ciclo de días configurable
- [x] Multiidioma (8 idiomas) - [x] Multiidioma (8 idiomas)
- [x] Sonido configurable independiente del canal Android - [x] Sonido configurable independiente del canal Android
- [x] Widget de pantalla de inicio
- [ ] Estadísticas de cumplimiento - [ ] Estadísticas de cumplimiento
- [ ] Widget de pantalla de inicio - [ ] Widget de pantalla de inicio
- [ ] Backup en la nube - [ ] Backup en la nube

Ver fichero

@@ -37,6 +37,11 @@ android {
buildFeatures { buildFeatures {
compose = true compose = true
} }
lint {
lintConfig = file("lint.xml")
warningsAsErrors = false
abortOnError = false
}
} }
dependencies { dependencies {

11
app/lint.xml Archivo normal
Ver fichero

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Advertencias de versiones disponibles: se mantienen como info, no error -->
<issue id="GradleDependency" severity="informational" />
<issue id="NewerVersionAvailable" severity="informational" />
<issue id="AndroidGradlePluginVersion" severity="informational" />
<!-- PluralsCandidate: las strings de resumen son intencionales (formato compacto) -->
<issue id="PluralsCandidate" severity="informational" />
</lint>

Ver fichero

@@ -19,7 +19,6 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Motivame"> android:theme="@style/Theme.Motivame">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -27,6 +26,31 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Widget -->
<receiver
android:name=".widget.MotivameWidget"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.manalejandro.motivame.WIDGET_REFRESH" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/motivame_widget_info" />
</receiver>
<!-- Reprogramar recordatorios tras reinicio del dispositivo o actualización de la app -->
<receiver
android:name=".receiver.BootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

Ver fichero

@@ -9,7 +9,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.work.* import androidx.work.WorkManager
import com.manalejandro.motivame.data.Task import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.data.TaskRepository import com.manalejandro.motivame.data.TaskRepository
import com.manalejandro.motivame.ui.screens.AddTaskScreen import com.manalejandro.motivame.ui.screens.AddTaskScreen
@@ -18,14 +18,11 @@ import com.manalejandro.motivame.ui.screens.SettingsScreen
import com.manalejandro.motivame.ui.theme.MotivameTheme import com.manalejandro.motivame.ui.theme.MotivameTheme
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
import com.manalejandro.motivame.util.LocaleHelper import com.manalejandro.motivame.util.LocaleHelper
import com.manalejandro.motivame.worker.DailyReminderWorker import com.manalejandro.motivame.worker.ReminderScheduler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.concurrent.TimeUnit
import kotlin.random.Random
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -55,112 +52,23 @@ class MainActivity : ComponentActivity() {
/** /**
* Cancela todos los workers anteriores y programa nuevos recordatorios * Cancela todos los workers anteriores y programa nuevos recordatorios
* para cada tarea activa, distribuyendo los avisos entre las 9:00 y las 21:00. * para cada tarea activa usando [ReminderScheduler].
* @param notificationsEnabled valor ya conocido, para evitar condición de carrera con DataStore. * @param notificationsEnabled valor ya conocido; si es null se lee de DataStore.
* Si es null, se lee del DataStore (solo al arrancar la app).
*/ */
fun scheduleAllReminders(notificationsEnabled: Boolean? = null) { fun scheduleAllReminders(notificationsEnabled: Boolean? = null) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val repository = TaskRepository(applicationContext) 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() val enabled = notificationsEnabled ?: repository.notificationEnabled.first()
if (!enabled) return@launch if (!enabled) {
WorkManager.getInstance(applicationContext).cancelAllWorkByTag("task_reminder")
return@launch
}
val tasks = repository.tasks.first() val tasks = repository.tasks.first()
tasks.filter { it.isActive }.forEach { task -> ReminderScheduler.scheduleAll(applicationContext, tasks)
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()
workManager.enqueue(workRequest)
}
}
/**
* Calcula el retardo hasta la hora indicada más un desplazamiento de días.
* Si la hora ya pasó hoy, se mueve al día siguiente antes de aplicar el offset.
*/
private fun calculateDelayToTimeWithDayOffset(hour: Int, minute: Int, dayOffset: Int): Long {
val now = System.currentTimeMillis()
val calendar = Calendar.getInstance().apply {
timeInMillis = now
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
// Si ya pasó esa hora hoy, mover a mañana antes de aplicar el offset
if (calendar.timeInMillis <= now) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
// Aplicar el offset de días adicionales
if (dayOffset > 0) {
calendar.add(Calendar.DAY_OF_YEAR, dayOffset)
}
return calendar.timeInMillis - now
}
}
@Composable @Composable
fun MotivameApp(onRescheduleReminders: (Boolean) -> Unit = {}) { fun MotivameApp(onRescheduleReminders: (Boolean) -> Unit = {}) {
val viewModel: TaskViewModel = viewModel() val viewModel: TaskViewModel = viewModel()

Ver fichero

@@ -0,0 +1,35 @@
package com.manalejandro.motivame.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.manalejandro.motivame.data.TaskRepository
import com.manalejandro.motivame.worker.ReminderScheduler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
* Reprograma todos los recordatorios activos cuando el dispositivo arranca
* o cuando se actualiza la app (acción MY_PACKAGE_REPLACED).
*/
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
if (action != Intent.ACTION_BOOT_COMPLETED &&
action != Intent.ACTION_MY_PACKAGE_REPLACED
) return
CoroutineScope(Dispatchers.IO).launch {
val repository = TaskRepository(context)
val enabled = repository.notificationEnabled.first()
if (!enabled) return@launch
val tasks = repository.tasks.first()
ReminderScheduler.scheduleAll(context, tasks)
}
}
}

Ver fichero

@@ -4,8 +4,8 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.net.toUri
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -361,7 +361,7 @@ fun SettingsScreen(
color = MaterialTheme.colorScheme.onTertiaryContainer, color = MaterialTheme.colorScheme.onTertiaryContainer,
textDecoration = TextDecoration.Underline, textDecoration = TextDecoration.Underline,
modifier = Modifier.clickable { modifier = Modifier.clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://manalejandro.com")) val intent = Intent(Intent.ACTION_VIEW, "https://manalejandro.com".toUri())
context.startActivity(intent) context.startActivity(intent)
} }
) )
@@ -391,7 +391,7 @@ fun SettingsScreen(
color = MaterialTheme.colorScheme.onTertiaryContainer, color = MaterialTheme.colorScheme.onTertiaryContainer,
textDecoration = TextDecoration.Underline, textDecoration = TextDecoration.Underline,
modifier = Modifier.clickable { modifier = Modifier.clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubUrl)) val intent = Intent(Intent.ACTION_VIEW, githubUrl.toUri())
context.startActivity(intent) context.startActivity(intent)
} }
) )

Ver fichero

@@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.manalejandro.motivame.data.Task import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.data.TaskRepository import com.manalejandro.motivame.data.TaskRepository
import com.manalejandro.motivame.widget.MotivameWidget
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -65,6 +66,7 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch { viewModelScope.launch {
repository.addTask(task) repository.addTask(task)
onRescheduleReminders?.invoke(_notificationEnabled.value) onRescheduleReminders?.invoke(_notificationEnabled.value)
MotivameWidget.requestUpdate(getApplication())
} }
} }
@@ -72,6 +74,7 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch { viewModelScope.launch {
repository.updateTask(task) repository.updateTask(task)
onRescheduleReminders?.invoke(_notificationEnabled.value) onRescheduleReminders?.invoke(_notificationEnabled.value)
MotivameWidget.requestUpdate(getApplication())
} }
} }
@@ -79,6 +82,7 @@ class TaskViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch { viewModelScope.launch {
repository.deleteTask(taskId) repository.deleteTask(taskId)
onRescheduleReminders?.invoke(_notificationEnabled.value) onRescheduleReminders?.invoke(_notificationEnabled.value)
MotivameWidget.requestUpdate(getApplication())
} }
} }

Ver fichero

@@ -1,5 +1,6 @@
package com.manalejandro.motivame.util package com.manalejandro.motivame.util
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import java.util.Locale import java.util.Locale
@@ -19,6 +20,7 @@ object LocaleHelper {
Language("ko", "🇰🇷", "한국어") Language("ko", "🇰🇷", "한국어")
) )
@SuppressLint("AppBundleLocaleChanges")
fun applyLocale(context: Context, languageCode: String): Context { fun applyLocale(context: Context, languageCode: String): Context {
val locale = Locale(languageCode) val locale = Locale(languageCode)
Locale.setDefault(locale) Locale.setDefault(locale)

Ver fichero

@@ -0,0 +1,198 @@
package com.manalejandro.motivame.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.RemoteViews
import com.manalejandro.motivame.MainActivity
import com.manalejandro.motivame.R
import com.manalejandro.motivame.data.Task
import com.manalejandro.motivame.data.TaskRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class MotivameWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { updateWidget(context, appWidgetManager, it) }
}
override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle
) {
updateWidget(context, appWidgetManager, appWidgetId)
}
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (intent.action == ACTION_REFRESH) {
val mgr = AppWidgetManager.getInstance(context)
mgr.getAppWidgetIds(ComponentName(context, MotivameWidget::class.java))
.forEach { updateWidget(context, mgr, it) }
}
}
companion object {
const val ACTION_REFRESH = "com.manalejandro.motivame.WIDGET_REFRESH"
// ── Número de tareas según altura real (MIN_HEIGHT) ─────────────
// ~1 fila ≈ 74dp, ~2 filas ≈ 148dp, ~3 filas ≈ 222dp, ~4 filas ≈ 296dp
private fun taskCount(heightDp: Int) = when {
heightDp >= 220 -> 3
heightDp >= 145 -> 2
else -> 1
}
// ── Número de metas por tarea según espacio disponible ───────────
// Se divide la altura disponible entre el número de tareas para estimar
// el espacio por tarea y decidir cuántas metas caben.
private fun goalsPerTask(heightDp: Int, tasks: Int): Int {
val spacePerTask = heightDp / tasks
return when {
spacePerTask >= 160 -> 3 // mucho espacio → 3 metas
spacePerTask >= 100 -> 2 // espacio medio → 2 metas
else -> 1 // poco espacio → 1 meta
}
}
private fun layoutFor(tasks: Int) = when (tasks) {
3 -> R.layout.widget_motivame_large
2 -> R.layout.widget_motivame_medium
else -> R.layout.widget_motivame_small
}
// IDs agrupados por tarea y slot de meta
private val TITLE_IDS = intArrayOf(
R.id.widget_task_title, R.id.widget_task2_title, R.id.widget_task3_title
)
private val GOAL_IDS = arrayOf(
intArrayOf(R.id.widget_t1_goal1, R.id.widget_t1_goal2, R.id.widget_t1_goal3),
intArrayOf(R.id.widget_t2_goal1, R.id.widget_t2_goal2, R.id.widget_t2_goal3),
intArrayOf(R.id.widget_t3_goal1, R.id.widget_t3_goal2, R.id.widget_t3_goal3)
)
private val CHIP_IDS = intArrayOf(
R.id.widget_chip1, R.id.widget_chip2, R.id.widget_chip3
)
fun updateWidget(context: Context, mgr: AppWidgetManager, widgetId: Int) {
CoroutineScope(Dispatchers.IO).launch {
val options = mgr.getAppWidgetOptions(widgetId)
// MIN_HEIGHT = altura real actual del widget en el launcher
val heightDp = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 110)
val numTasks = taskCount(heightDp)
val numGoals = goalsPerTask(heightDp, numTasks)
val showChip = numTasks == 3 // chip solo en layout grande
val activeTasks = TaskRepository(context).tasks.first().filter { it.isActive }
val views = RemoteViews(context.packageName, layoutFor(numTasks))
// Click → abrir app
views.setOnClickPendingIntent(
R.id.widget_root,
PendingIntent.getActivity(
context, widgetId,
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setTextViewText(R.id.widget_hint,
context.getString(R.string.widget_tap_to_open))
if (activeTasks.isEmpty()) {
views.setTextViewText(R.id.widget_status, "")
views.setTextViewText(R.id.widget_task_title, "Motívame")
views.setTextViewText(R.id.widget_t1_goal1,
context.getString(R.string.widget_no_tasks))
// ocultar metas 2 y 3 del slot 1
views.setViewVisibility(R.id.widget_t1_goal2, View.GONE)
views.setViewVisibility(R.id.widget_t1_goal3, View.GONE)
// ocultar slots extra
if (numTasks >= 2) hideTaskSlot(views, 1, showChip)
if (numTasks >= 3) hideTaskSlot(views, 2, showChip)
} else {
views.setTextViewText(R.id.widget_status, "")
for (slot in 0 until numTasks) {
val task = activeTasks.getOrNull(slot)
fillTaskSlot(context, views, slot, task, numGoals, showChip)
}
}
mgr.updateAppWidget(widgetId, views)
}
}
private fun fillTaskSlot(
context: Context,
views: RemoteViews,
slot: Int, // 0-based
task: Task?,
numGoals: Int,
showChip: Boolean
) {
val titleId = TITLE_IDS[slot]
val goalIds = GOAL_IDS[slot]
if (task == null) {
hideTaskSlot(views, slot, showChip)
return
}
views.setViewVisibility(titleId, View.VISIBLE)
views.setTextViewText(titleId, task.title)
// Rellenar metas con opacidad decreciente; ocultar las que excedan numGoals
val goals = task.goals
for (i in 0..2) {
val goalId = goalIds[i]
if (i < numGoals && i < goals.size) {
views.setViewVisibility(goalId, View.VISIBLE)
views.setTextViewText(goalId, "🎯 ${goals[i]}")
} else {
views.setViewVisibility(goalId, View.GONE)
}
}
// Chip de avisos (solo layout grande)
if (showChip) {
val chipId = CHIP_IDS[slot]
val chipText = if (task.repeatEveryDays == 1)
context.getString(R.string.task_summary_reminders_daily, task.dailyReminders)
else
context.getString(R.string.task_summary_reminders, task.dailyReminders, task.repeatEveryDays)
views.setViewVisibility(chipId, View.VISIBLE)
views.setTextViewText(chipId, "🔔 $chipText")
}
}
private fun hideTaskSlot(views: RemoteViews, slot: Int, showChip: Boolean) {
views.setViewVisibility(TITLE_IDS[slot], View.GONE)
GOAL_IDS[slot].forEach { views.setViewVisibility(it, View.GONE) }
if (showChip) views.setViewVisibility(CHIP_IDS[slot], View.GONE)
}
fun requestUpdate(context: Context) {
context.sendBroadcast(
Intent(context, MotivameWidget::class.java).apply {
action = ACTION_REFRESH
}
)
}
}
}

Ver fichero

@@ -5,6 +5,7 @@ import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.manalejandro.motivame.data.TaskRepository import com.manalejandro.motivame.data.TaskRepository
import com.manalejandro.motivame.notifications.NotificationHelper import com.manalejandro.motivame.notifications.NotificationHelper
import com.manalejandro.motivame.widget.MotivameWidget
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
class DailyReminderWorker( class DailyReminderWorker(
@@ -14,6 +15,9 @@ class DailyReminderWorker(
companion object { companion object {
const val KEY_TASK_ID = "task_id" const val KEY_TASK_ID = "task_id"
const val KEY_SCHEDULE_HOUR = "schedule_hour"
const val KEY_SCHEDULE_MINUTE = "schedule_minute"
const val KEY_CYCLE_DAYS = "cycle_days"
} }
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
@@ -36,9 +40,32 @@ class DailyReminderWorker(
taskToNotify?.let { taskToNotify?.let {
notificationHelper.sendTaskReminder(it, soundEnabled) notificationHelper.sendTaskReminder(it, soundEnabled)
// ✅ Auto-reprogramar este mismo aviso para el siguiente ciclo
val hour = inputData.getInt(KEY_SCHEDULE_HOUR, -1)
val minute = inputData.getInt(KEY_SCHEDULE_MINUTE, -1)
val cycleDays = inputData.getInt(KEY_CYCLE_DAYS, it.repeatEveryDays)
if (hour >= 0 && minute >= 0) {
// El siguiente disparo es exactamente [cycleDays] días después,
// a la misma hora local — se calcula con dayOffset = 0 porque
// la hora ya quedó en el futuro (hoy+cycleDays).
val delayMs = ReminderScheduler.calculateDelay(hour, minute, cycleDays)
ReminderScheduler.enqueueOne(
context = applicationContext,
taskId = it.id,
hour = hour,
minute = minute,
cycleDays = cycleDays,
dayOffset = 0,
delayMs = delayMs
)
} }
}
// Refrescar el widget con la meta actualizada
MotivameWidget.requestUpdate(applicationContext)
return Result.success() return Result.success()
} }
} }

Ver fichero

@@ -0,0 +1,142 @@
package com.manalejandro.motivame.worker
import android.content.Context
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.manalejandro.motivame.data.Task
import java.util.Calendar
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import kotlin.random.Random
/**
* Centraliza toda la lógica para programar/cancelar recordatorios con WorkManager.
* Usa siempre la zona horaria local del dispositivo de forma explícita.
*/
object ReminderScheduler {
// Ventana de notificaciones: 9:00 a 21:00 (hora local)
private const val WINDOW_START_MINUTE = 9 * 60 // 540
private const val WINDOW_END_MINUTE = 21 * 60 // 1260
private const val WINDOW_SIZE = WINDOW_END_MINUTE - WINDOW_START_MINUTE // 720
/** Encola un único recordatorio con los parámetros exactos dados. */
fun enqueueOne(
context: Context,
taskId: String,
hour: Int,
minute: Int,
cycleDays: Int,
dayOffset: Int,
delayMs: Long
) {
val inputData = workDataOf(
DailyReminderWorker.KEY_TASK_ID to taskId,
DailyReminderWorker.KEY_SCHEDULE_HOUR to hour,
DailyReminderWorker.KEY_SCHEDULE_MINUTE to minute,
DailyReminderWorker.KEY_CYCLE_DAYS to cycleDays
)
val workRequest = OneTimeWorkRequestBuilder<DailyReminderWorker>()
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.addTag("task_reminder")
.addTag("task_$taskId")
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
/** Cancela todos los workers existentes y programa nuevos para cada tarea activa. */
fun scheduleAll(context: Context, tasks: List<Task>) {
val workManager = WorkManager.getInstance(context)
workManager.cancelAllWorkByTag("task_reminder")
tasks.filter { it.isActive }.forEach { task ->
scheduleForTask(context, task)
}
}
/** Programa todos los recordatorios para una tarea concreta. */
fun scheduleForTask(context: Context, task: Task) {
val reminders = task.dailyReminders.coerceIn(1, 10)
val cycleDays = task.repeatEveryDays.coerceIn(1, 30)
val workManager = WorkManager.getInstance(context)
// Distribuir N avisos en días del ciclo (uno por día si reminders ≤ cycleDays)
val dayAssignments = (0 until reminders).map { i -> i % cycleDays }
// Generar horas aleatorias únicas dentro de la ventana [9:00, 21:00)
val usedMinutes = mutableSetOf<Int>()
val minuteAssignments = mutableListOf<Int>()
repeat(reminders) {
var candidate: Int
var attempts = 0
do {
candidate = WINDOW_START_MINUTE + Random.nextInt(WINDOW_SIZE)
attempts++
} while (usedMinutes.contains(candidate) && attempts < WINDOW_SIZE)
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 = calculateDelay(targetHour, targetMinute, dayOffset)
val inputData = workDataOf(
DailyReminderWorker.KEY_TASK_ID to task.id,
DailyReminderWorker.KEY_SCHEDULE_HOUR to targetHour,
DailyReminderWorker.KEY_SCHEDULE_MINUTE to targetMinute,
DailyReminderWorker.KEY_CYCLE_DAYS to cycleDays
)
val workRequest = OneTimeWorkRequestBuilder<DailyReminderWorker>()
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.addTag("task_reminder")
.addTag("task_${task.id}")
.build()
workManager.enqueue(workRequest)
}
}
/**
* Calcula el retardo (ms) hasta la hora indicada en HORA LOCAL,
* usando TimeZone.getDefault() de forma explícita para evitar
* que el contexto del hilo de background use UTC u otra zona.
*
* Si la hora ya pasó hoy, se avanza al día siguiente y luego
* se aplica el [dayOffset] adicional del ciclo.
*/
fun calculateDelay(hour: Int, minute: Int, dayOffset: Int): Long {
val now = System.currentTimeMillis()
// ✅ Zona horaria local explícita — clave para que funcione
// correctamente desde hilos de background y tras reinicio.
val calendar = Calendar.getInstance(TimeZone.getDefault()).apply {
timeInMillis = now
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
// Si esa hora ya pasó hoy, mover al día siguiente
if (calendar.timeInMillis <= now) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
// Desplazamiento adicional del ciclo
if (dayOffset > 0) {
calendar.add(Calendar.DAY_OF_YEAR, dayOffset)
}
return calendar.timeInMillis - now
}
}

Ver fichero

@@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Ver fichero

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Estrella de motivación - icono monocromo para Android 13+ -->
<path
android:fillColor="#FFFFFF"
android:pathData="M54,20 L59.5,40 L80,40 L63.5,52 L70,72 L54,60 L38,72 L44.5,52 L28,40 L48.5,40 Z" />
</vector>

Ver fichero

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#6366F1"
android:endColor="#8B5CF6"
android:angle="135"
android:type="linear" />
<corners android:radius="16dp" />
</shape>

Ver fichero

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#26FFFFFF" />
<corners android:radius="10dp" />
</shape>

Ver fichero

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/widget_background"
android:padding="16dp"
android:clickable="true"
android:focusable="true">
<!-- Cabecera: icono + nombre de la app -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/widget_app_name"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold"
android:alpha="0.8" />
<TextView
android:id="@+id/widget_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/widget_status_indicator"
android:textColor="#10B981"
android:textSize="11sp" />
</LinearLayout>
<!-- Título de la tarea -->
<TextView
android:id="@+id/widget_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_task_default"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="6dp" />
<!-- Meta aleatoria -->
<TextView
android:id="@+id/widget_goal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/widget_goal_default"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:alpha="0.85"
android:maxLines="2"
android:ellipsize="end" />
<!-- Pie: toca para abrir -->
<TextView
android:id="@+id/widget_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_tap_to_open"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.55"
android:gravity="end"
android:layout_marginTop="4dp" />
</LinearLayout>

Ver fichero

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Layout GRANDE: 3 tareas · 3 metas por tarea + chip de avisos -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/widget_background"
android:padding="14dp"
android:clickable="true"
android:focusable="true">
<!-- Cabecera -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/widget_app_name"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold"
android:alpha="0.8" />
<TextView
android:id="@+id/widget_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/widget_status_indicator"
android:textColor="#10B981"
android:textSize="11sp" />
</LinearLayout>
<!-- Tarea 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp"
android:layout_marginBottom="5dp">
<TextView
android:id="@+id/widget_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="3dp" />
<TextView
android:id="@+id/widget_t1_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/widget_t1_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_t1_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_chip1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.6"
android:layout_marginTop="3dp"
android:visibility="gone" />
</LinearLayout>
<!-- Tarea 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp"
android:layout_marginBottom="5dp">
<TextView
android:id="@+id/widget_task2_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="3dp" />
<TextView
android:id="@+id/widget_t2_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/widget_t2_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_t2_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_chip2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.6"
android:layout_marginTop="3dp"
android:visibility="gone" />
</LinearLayout>
<!-- Tarea 3 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/widget_task3_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="3dp" />
<TextView
android:id="@+id/widget_t3_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/widget_t3_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_t3_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_chip3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.6"
android:layout_marginTop="3dp"
android:visibility="gone" />
</LinearLayout>
<!-- Pie -->
<TextView
android:id="@+id/widget_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.5"
android:gravity="end" />
</LinearLayout>

Ver fichero

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Layout MEDIANO: 2 tareas · 2 metas por tarea -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/widget_background"
android:padding="14dp"
android:clickable="true"
android:focusable="true">
android:focusable="true">
<!-- Cabecera -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/widget_app_name"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:textStyle="bold"
android:alpha="0.8" />
<TextView
android:id="@+id/widget_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/widget_status_indicator"
android:textColor="#10B981"
android:textSize="11sp" />
</LinearLayout>
<!-- Tarea 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp"
android:layout_marginBottom="6dp">
<TextView
android:id="@+id/widget_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="3dp" />
<TextView
android:id="@+id/widget_t1_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/widget_t1_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_t1_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
</LinearLayout>
<!-- Tarea 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/widget_task2_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="3dp" />
<TextView
android:id="@+id/widget_t2_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/widget_t2_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
<TextView
android:id="@+id/widget_t2_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:visibility="gone" />
</LinearLayout>
<!-- Pie -->
<TextView
android:id="@+id/widget_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.5"
android:gravity="end" />
</LinearLayout>

Ver fichero

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Layout PEQUEÑO: 1 tarea · 1 meta visible -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/widget_background"
android:padding="12dp"
android:clickable="true"
android:focusable="true">
<!-- Cabecera -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="6dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/widget_app_name"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:textStyle="bold"
android:alpha="0.8" />
<TextView
android:id="@+id/widget_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/widget_status_indicator"
android:textColor="#10B981"
android:textSize="11sp" />
</LinearLayout>
<!-- Tarea 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/widget_task_bg"
android:padding="8dp">
<TextView
android:id="@+id/widget_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginBottom="4dp" />
<!-- Meta 1 (siempre visible) -->
<TextView
android:id="@+id/widget_t1_goal1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:alpha="0.85"
android:maxLines="1"
android:ellipsize="end" />
<!-- Meta 2 (oculta en small) -->
<TextView
android:id="@+id/widget_t1_goal2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:alpha="0.75"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone" />
<!-- Meta 3 (oculta en small) -->
<TextView
android:id="@+id/widget_t1_goal3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:alpha="0.65"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone" />
</LinearLayout>
<!-- Pie -->
<TextView
android:id="@+id/widget_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:alpha="0.5"
android:gravity="end"
android:layout_marginTop="4dp" />
</LinearLayout>

Ver fichero

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

Ver fichero

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">Füge deine erste Aufgabe und Ziele hinzu, um motiviert zu bleiben</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="goals_label">🎯 Ziele:</string>
<string name="task_paused">⏸️ Pausiert</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_desc">Löschen</string>
<string name="delete_task_title">Aufgabe 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_task_confirm">Möchtest du \'%1$s\' wirklich löschen?</string>
@@ -72,9 +71,17 @@
<string name="language_section">🌐 Sprache</string> <string name="language_section">🌐 Sprache</string>
<string name="language_desc">Wähle die Sprache der Anwendung</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_default_message">Denke daran, diese Aufgabe abzuschließen!</string>
<string name="notification_big_text">📝 Aufgabe: %1$s\n\n🎯 Denk daran: %2$s</string> <string name="notification_big_text">📝 Aufgabe: %1$s\n\n🎯 Denk daran: %2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ Motivier mich</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">Aufgabe</string>
<string name="widget_goal_default">🎯 Ziel</string>
<string name="widget_description">Zeigt deine aktive Aufgabe und ein motivierendes Ziel</string>
<string name="widget_no_tasks">Keine aktiven Aufgaben.\nÖffne Motivier mich, um eine hinzuzufügen.</string>
<string name="widget_tap_to_open">Tippen zum Öffnen →</string>
</resources> </resources>

Ver fichero

@@ -11,7 +11,6 @@
<string name="empty_state_subtitle">Add your first task and goals to stay motivated</string> <string name="empty_state_subtitle">Add your first task and goals to stay motivated</string>
<string name="goals_label">🎯 Goals:</string> <string name="goals_label">🎯 Goals:</string>
<string name="task_paused">⏸️ Paused</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_desc">Delete</string>
<string name="delete_task_title">Delete task</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_task_confirm">Are you sure you want to delete \'%1$s\'?</string>
@@ -76,11 +75,20 @@
<!-- Language selector --> <!-- Language selector -->
<string name="language_section">🌐 Language</string> <string name="language_section">🌐 Language</string>
<string name="language_desc">Select the application 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 --> <!-- Notifications -->
<string name="notification_default_message">Remember to complete this task!</string> <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> <string name="notification_big_text">📝 Task: %1$s\n\n🎯 Remember: %2$s</string>
<!-- Widget -->
<!-- Widget -->
<string name="widget_app_name">⭐ Motivate Me</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">Task</string>
<string name="widget_goal_default">🎯 Goal</string>
<string name="widget_description">Shows your active task and a motivational goal</string>
<string name="widget_no_tasks">No active tasks.\nOpen Motivate Me to add one.</string>
<string name="widget_tap_to_open">Tap to open →</string>
</resources> </resources>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">Ajoutez votre première tâche et vos objectifs pour rester motivé</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="goals_label">🎯 Objectifs :</string>
<string name="task_paused">⏸️ En pause</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_desc">Supprimer</string>
<string name="delete_task_title">Supprimer la tâche</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_task_confirm">Voulez-vous vraiment supprimer \'%1$s\' ?</string>
@@ -72,9 +71,17 @@
<string name="language_section">🌐 Langue</string> <string name="language_section">🌐 Langue</string>
<string name="language_desc">Sélectionnez la langue de l\'application</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_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> <string name="notification_big_text">📝 Tâche : %1$s\n\n🎯 Rappel : %2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ Motivez-moi</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">Tâche</string>
<string name="widget_goal_default">🎯 Objectif</string>
<string name="widget_description">Affiche votre tâche active et un objectif motivationnel</string>
<string name="widget_no_tasks">Aucune tâche active.\nOuvrez Motivez-moi pour en ajouter une.</string>
<string name="widget_tap_to_open">Toucher pour ouvrir →</string>
</resources> </resources>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">最初のタスクと目標を追加してモチベーションを維持しよう</string> <string name="empty_state_subtitle">最初のタスクと目標を追加してモチベーションを維持しよう</string>
<string name="goals_label">🎯 目標:</string> <string name="goals_label">🎯 目標:</string>
<string name="task_paused">⏸️ 一時停止中</string> <string name="task_paused">⏸️ 一時停止中</string>
<string name="toggle_active_desc">タスクの有効/一時停止を切り替え</string>
<string name="delete_task_desc">削除</string> <string name="delete_task_desc">削除</string>
<string name="delete_task_title">タスクを削除</string> <string name="delete_task_title">タスクを削除</string>
<string name="delete_task_confirm">\'%1$s\' を削除してもよろしいですか?</string> <string name="delete_task_confirm">\'%1$s\' を削除してもよろしいですか?</string>
@@ -72,9 +71,17 @@
<string name="language_section">🌐 言語</string> <string name="language_section">🌐 言語</string>
<string name="language_desc">アプリケーションの言語を選択してください</string> <string name="language_desc">アプリケーションの言語を選択してください</string>
<string name="language_restart_hint">言語を適用するためにアプリが再起動します</string>
<string name="notification_default_message">このタスクを完了することを忘れずに!</string> <string name="notification_default_message">このタスクを完了することを忘れずに!</string>
<string name="notification_big_text">📝 タスク:%1$s\n\n🎯 リマインダー:%2$s</string> <string name="notification_big_text">📝 タスク:%1$s\n\n🎯 リマインダー:%2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ やる気アップ</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">タスク</string>
<string name="widget_goal_default">🎯 目標</string>
<string name="widget_description">アクティブなタスクとモチベーション目標を表示</string>
<string name="widget_no_tasks">アクティブなタスクがありません。\nやる気アップを開いて追加してください。</string>
<string name="widget_tap_to_open">タップして開く →</string>
</resources> </resources>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">첫 번째 작업과 목표를 추가하여 동기를 유지하세요</string> <string name="empty_state_subtitle">첫 번째 작업과 목표를 추가하여 동기를 유지하세요</string>
<string name="goals_label">🎯 목표:</string> <string name="goals_label">🎯 목표:</string>
<string name="task_paused">⏸️ 일시 중지됨</string> <string name="task_paused">⏸️ 일시 중지됨</string>
<string name="toggle_active_desc">작업 활성화/일시 중지</string>
<string name="delete_task_desc">삭제</string> <string name="delete_task_desc">삭제</string>
<string name="delete_task_title">작업 삭제</string> <string name="delete_task_title">작업 삭제</string>
<string name="delete_task_confirm">\'%1$s\'을(를) 정말 삭제하시겠습니까?</string> <string name="delete_task_confirm">\'%1$s\'을(를) 정말 삭제하시겠습니까?</string>
@@ -72,9 +71,17 @@
<string name="language_section">🌐 언어</string> <string name="language_section">🌐 언어</string>
<string name="language_desc">애플리케이션 언어를 선택하세요</string> <string name="language_desc">애플리케이션 언어를 선택하세요</string>
<string name="language_restart_hint">언어 적용을 위해 앱이 재시작됩니다</string>
<string name="notification_default_message">이 작업을 완료하는 것을 잊지 마세요!</string> <string name="notification_default_message">이 작업을 완료하는 것을 잊지 마세요!</string>
<string name="notification_big_text">📝 작업: %1$s\n\n🎯 알림: %2$s</string> <string name="notification_big_text">📝 작업: %1$s\n\n🎯 알림: %2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ 동기부여</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">작업</string>
<string name="widget_goal_default">🎯 목표</string>
<string name="widget_description">활성 작업과 동기 부여 목표를 표시합니다</string>
<string name="widget_no_tasks">활성 작업이 없습니다.\n동기부여 앱을 열어 추가하세요.</string>
<string name="widget_tap_to_open">탭하여 열기 →</string>
</resources> </resources>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">Adicione sua primeira tarefa e metas para se manter motivado</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="goals_label">🎯 Metas:</string>
<string name="task_paused">⏸️ Pausada</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_desc">Excluir</string>
<string name="delete_task_title">Excluir tarefa</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_task_confirm">Tem certeza que deseja excluir \'%1$s\'?</string>
@@ -72,9 +71,17 @@
<string name="language_section">🌐 Idioma</string> <string name="language_section">🌐 Idioma</string>
<string name="language_desc">Selecione o idioma do aplicativo</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_default_message">Lembre-se de completar esta tarefa!</string>
<string name="notification_big_text">📝 Tarefa: %1$s\n\n🎯 Lembre-se: %2$s</string> <string name="notification_big_text">📝 Tarefa: %1$s\n\n🎯 Lembre-se: %2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ Motiva-me</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">Tarefa</string>
<string name="widget_goal_default">🎯 Meta</string>
<string name="widget_description">Mostra sua tarefa ativa e uma meta motivacional</string>
<string name="widget_no_tasks">Sem tarefas ativas.\nAbra Motiva-me para adicionar uma.</string>
<string name="widget_tap_to_open">Toque para abrir →</string>
</resources> </resources>

Ver fichero

@@ -11,7 +11,6 @@
<string name="empty_state_subtitle">添加你的第一个任务和目标,保持动力</string> <string name="empty_state_subtitle">添加你的第一个任务和目标,保持动力</string>
<string name="goals_label">🎯 目标:</string> <string name="goals_label">🎯 目标:</string>
<string name="task_paused">⏸️ 已暂停</string> <string name="task_paused">⏸️ 已暂停</string>
<string name="toggle_active_desc">切换任务状态</string>
<string name="delete_task_desc">删除</string> <string name="delete_task_desc">删除</string>
<string name="delete_task_title">删除任务</string> <string name="delete_task_title">删除任务</string>
<string name="delete_task_confirm">确定要删除 \'%1$s\' 吗?</string> <string name="delete_task_confirm">确定要删除 \'%1$s\' 吗?</string>
@@ -76,11 +75,19 @@
<!-- Language selector --> <!-- Language selector -->
<string name="language_section">🌐 语言</string> <string name="language_section">🌐 语言</string>
<string name="language_desc">选择应用程序语言</string> <string name="language_desc">选择应用程序语言</string>
<string name="language_restart_hint">应用将重启以应用语言更改</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_default_message">记得完成这个任务!</string> <string name="notification_default_message">记得完成这个任务!</string>
<string name="notification_big_text">📝 任务:%1$s\n\n🎯 提醒:%2$s</string> <string name="notification_big_text">📝 任务:%1$s\n\n🎯 提醒:%2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ 激励我</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">任务</string>
<string name="widget_goal_default">🎯 目标</string>
<string name="widget_description">显示您的活跃任务和激励目标</string>
<string name="widget_no_tasks">没有活跃任务。\n打开激励我来添加一个。</string>
<string name="widget_tap_to_open">点击打开 →</string>
</resources> </resources>

Ver fichero

@@ -1,10 +1,3 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources> </resources>

Ver fichero

@@ -10,7 +10,6 @@
<string name="empty_state_subtitle">Agrega tu primera tarea y metas para mantenerte motivado</string> <string name="empty_state_subtitle">Agrega tu primera tarea y metas para mantenerte motivado</string>
<string name="goals_label">🎯 Metas:</string> <string name="goals_label">🎯 Metas:</string>
<string name="task_paused">⏸️ Pausada</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_desc">Eliminar</string>
<string name="delete_task_title">Eliminar tarea</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_task_confirm">¿Estás seguro de que quieres eliminar \'%1$s\'?</string>
@@ -75,9 +74,17 @@
<!-- Language selector --> <!-- Language selector -->
<string name="language_section">🌐 Idioma</string> <string name="language_section">🌐 Idioma</string>
<string name="language_desc">Selecciona el idioma de la aplicación</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 --> <!-- Notifications -->
<string name="notification_default_message">¡Recuerda completar esta tarea!</string> <string name="notification_default_message">¡Recuerda completar esta tarea!</string>
<string name="notification_big_text">📝 Tarea: %1$s\n\n🎯 Recuerda: %2$s</string> <string name="notification_big_text">📝 Tarea: %1$s\n\n🎯 Recuerda: %2$s</string>
<!-- Widget -->
<string name="widget_app_name">⭐ Motívame</string>
<string name="widget_status_indicator"></string>
<string name="widget_task_default">Tarea</string>
<string name="widget_goal_default">🎯 Meta</string>
<string name="widget_description">Muestra tu tarea activa y una meta motivacional</string>
<string name="widget_no_tasks">Sin tareas activas.\nAbre Motívame para añadir una.</string>
<string name="widget_tap_to_open">Toca para abrir →</string>
</resources> </resources>

Ver fichero

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:minWidth="250dp"
android:minHeight="110dp"
android:targetCellWidth="3"
android:targetCellHeight="2"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/widget_motivame"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:description="@string/widget_description"
android:previewLayout="@layout/widget_motivame"
tools:ignore="UnusedAttribute" />