@@ -0,0 +1,24 @@
|
||||
package com.manalejandro.motivame
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.manalejandro.motivame", appContext.packageName)
|
||||
}
|
||||
}
|
||||
31
app/src/main/AndroidManifest.xml
Archivo normal
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Motivame">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Motivame">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 67 KiB |
89
app/src/main/java/com/manalejandro/motivame/MainActivity.kt
Archivo normal
@@ -0,0 +1,89 @@
|
||||
package com.manalejandro.motivame
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.work.*
|
||||
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.worker.DailyReminderWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Configurar recordatorios diarios
|
||||
setupDailyReminders()
|
||||
|
||||
setContent {
|
||||
MotivameTheme {
|
||||
MotivameApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDailyReminders() {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
|
||||
.build()
|
||||
|
||||
val dailyWorkRequest = PeriodicWorkRequestBuilder<DailyReminderWorker>(
|
||||
1, TimeUnit.DAYS
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork(
|
||||
"daily_reminder",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
dailyWorkRequest
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (calendar.timeInMillis <= currentTime) {
|
||||
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1)
|
||||
}
|
||||
|
||||
return calendar.timeInMillis - currentTime
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MotivameApp() {
|
||||
val viewModel: TaskViewModel = viewModel()
|
||||
var currentScreen by remember { mutableStateOf("main") }
|
||||
|
||||
when (currentScreen) {
|
||||
"main" -> MainScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateToAddTask = { currentScreen = "add_task" },
|
||||
onNavigateToSettings = { currentScreen = "settings" }
|
||||
)
|
||||
"add_task" -> AddTaskScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { currentScreen = "main" }
|
||||
)
|
||||
"settings" -> SettingsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { currentScreen = "main" }
|
||||
)
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/manalejandro/motivame/data/Task.kt
Archivo normal
@@ -0,0 +1,12 @@
|
||||
package com.manalejandro.motivame.data
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
data class Task(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val title: String,
|
||||
val goals: List<String>,
|
||||
val isActive: Boolean = true,
|
||||
val createdAt: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
151
app/src/main/java/com/manalejandro/motivame/data/TaskRepository.kt
Archivo normal
@@ -0,0 +1,151 @@
|
||||
package com.manalejandro.motivame.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "motivame_prefs")
|
||||
|
||||
class TaskRepository(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private val TASKS_KEY = stringPreferencesKey("tasks")
|
||||
private val NOTIFICATION_ENABLED_KEY = stringPreferencesKey("notification_enabled")
|
||||
private val SOUND_ENABLED_KEY = stringPreferencesKey("sound_enabled")
|
||||
|
||||
val DEFAULT_TASKS = listOf(
|
||||
Task(
|
||||
title = "Hacer ejercicio",
|
||||
goals = listOf(
|
||||
"Mejorar mi salud cardiovascular",
|
||||
"Sentirme más energético",
|
||||
"Alcanzar mi peso ideal"
|
||||
)
|
||||
),
|
||||
Task(
|
||||
title = "Estudiar inglés",
|
||||
goals = listOf(
|
||||
"Conseguir mejores oportunidades laborales",
|
||||
"Viajar sin limitaciones",
|
||||
"Expandir mi conocimiento"
|
||||
)
|
||||
),
|
||||
Task(
|
||||
title = "Leer 30 minutos",
|
||||
goals = listOf(
|
||||
"Desarrollar el hábito de la lectura",
|
||||
"Aprender cosas nuevas",
|
||||
"Reducir el tiempo en redes sociales"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val tasks: Flow<List<Task>> = context.dataStore.data
|
||||
.map { preferences ->
|
||||
val tasksJson = preferences[TASKS_KEY]
|
||||
if (tasksJson.isNullOrEmpty()) {
|
||||
DEFAULT_TASKS
|
||||
} else {
|
||||
parseTasksFromJson(tasksJson)
|
||||
}
|
||||
}
|
||||
|
||||
val notificationEnabled: Flow<Boolean> = context.dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[NOTIFICATION_ENABLED_KEY]?.toBoolean() ?: true
|
||||
}
|
||||
|
||||
val soundEnabled: Flow<Boolean> = context.dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[SOUND_ENABLED_KEY]?.toBoolean() ?: true
|
||||
}
|
||||
|
||||
suspend fun saveTasks(tasks: List<Task>) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[TASKS_KEY] = tasksToJson(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addTask(task: Task) {
|
||||
context.dataStore.edit { preferences ->
|
||||
val currentTasks = parseTasksFromJson(preferences[TASKS_KEY] ?: "")
|
||||
val updatedTasks = currentTasks + task
|
||||
preferences[TASKS_KEY] = tasksToJson(updatedTasks)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTask(task: Task) {
|
||||
context.dataStore.edit { preferences ->
|
||||
val currentTasks = parseTasksFromJson(preferences[TASKS_KEY] ?: "")
|
||||
val updatedTasks = currentTasks.map { if (it.id == task.id) task else it }
|
||||
preferences[TASKS_KEY] = tasksToJson(updatedTasks)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteTask(taskId: String) {
|
||||
context.dataStore.edit { preferences ->
|
||||
val currentTasks = parseTasksFromJson(preferences[TASKS_KEY] ?: "")
|
||||
val updatedTasks = currentTasks.filter { it.id != taskId }
|
||||
preferences[TASKS_KEY] = tasksToJson(updatedTasks)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setNotificationEnabled(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[NOTIFICATION_ENABLED_KEY] = enabled.toString()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setSoundEnabled(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[SOUND_ENABLED_KEY] = enabled.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun tasksToJson(tasks: List<Task>): String {
|
||||
val jsonArray = JSONArray()
|
||||
tasks.forEach { task ->
|
||||
val jsonObject = JSONObject().apply {
|
||||
put("id", task.id)
|
||||
put("title", task.title)
|
||||
put("goals", JSONArray(task.goals))
|
||||
put("isActive", task.isActive)
|
||||
put("createdAt", task.createdAt)
|
||||
}
|
||||
jsonArray.put(jsonObject)
|
||||
}
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
private fun parseTasksFromJson(json: String): List<Task> {
|
||||
if (json.isEmpty()) return emptyList()
|
||||
|
||||
return try {
|
||||
val jsonArray = JSONArray(json)
|
||||
List(jsonArray.length()) { index ->
|
||||
val jsonObject = jsonArray.getJSONObject(index)
|
||||
val goalsArray = jsonObject.getJSONArray("goals")
|
||||
val goals = List(goalsArray.length()) { goalsArray.getString(it) }
|
||||
|
||||
Task(
|
||||
id = jsonObject.getString("id"),
|
||||
title = jsonObject.getString("title"),
|
||||
goals = goals,
|
||||
isActive = jsonObject.getBoolean("isActive"),
|
||||
createdAt = jsonObject.getLong("createdAt")
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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.RingtoneManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.manalejandro.motivame.MainActivity
|
||||
import com.manalejandro.motivame.R
|
||||
import com.manalejandro.motivame.data.Task
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
val motivationalMessage = if (task.goals.isNotEmpty()) {
|
||||
task.goals.random()
|
||||
} else {
|
||||
"¡Recuerda completar esta tarea!"
|
||||
}
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setContentTitle("⏰ ${task.title}")
|
||||
.setContentText(motivationalMessage)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(
|
||||
"📝 Tarea: ${task.title}\n\n🎯 Recuerda: $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)
|
||||
}
|
||||
|
||||
try {
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
notificationManager.notify(Random.nextInt(), notificationBuilder.build())
|
||||
} catch (e: SecurityException) {
|
||||
// El usuario no ha concedido permisos de notificación
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMotivationalReminder(tasks: List<Task>, withSound: Boolean = true) {
|
||||
if (tasks.isEmpty()) return
|
||||
|
||||
val activeTask = tasks.firstOrNull { it.isActive } ?: return
|
||||
sendTaskReminder(activeTask, withSound)
|
||||
}
|
||||
}
|
||||
|
||||
219
app/src/main/java/com/manalejandro/motivame/ui/screens/AddTaskScreen.kt
Archivo normal
@@ -0,0 +1,219 @@
|
||||
package com.manalejandro.motivame.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.manalejandro.motivame.data.Task
|
||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddTaskScreen(
|
||||
viewModel: TaskViewModel,
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
var taskTitle by remember { mutableStateOf("") }
|
||||
var currentGoal by remember { mutableStateOf("") }
|
||||
var goals by remember { mutableStateOf(listOf<String>()) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Nueva Tarea") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Volver")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "📝 ¿Qué debes recordar?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = taskTitle,
|
||||
onValueChange = { taskTitle = it },
|
||||
label = { Text("Título de la tarea") },
|
||||
placeholder = { Text("Ej: Hacer ejercicio") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Star, contentDescription = null)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🎯 ¿Qué esperas alcanzar?",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = currentGoal,
|
||||
onValueChange = { currentGoal = it },
|
||||
label = { Text("Nueva meta") },
|
||||
placeholder = { Text("Ej: Mejorar mi salud") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (currentGoal.isNotBlank()) {
|
||||
goals = goals + currentGoal.trim()
|
||||
currentGoal = ""
|
||||
}
|
||||
},
|
||||
enabled = currentGoal.isNotBlank()
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Agregar meta")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (goals.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Metas agregadas:",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
itemsIndexed(goals) { index, goal ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CheckCircle,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = goal,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
goals = goals.filterIndexed { i, _ -> i != index }
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Eliminar meta",
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
if (taskTitle.isNotBlank()) {
|
||||
val newTask = Task(
|
||||
title = taskTitle.trim(),
|
||||
goals = goals,
|
||||
isActive = true
|
||||
)
|
||||
viewModel.addTask(newTask)
|
||||
onNavigateBack()
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
enabled = taskTitle.isNotBlank(),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Check, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Guardar Tarea",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
252
app/src/main/java/com/manalejandro/motivame/ui/screens/MainScreen.kt
Archivo normal
@@ -0,0 +1,252 @@
|
||||
package com.manalejandro.motivame.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.manalejandro.motivame.data.Task
|
||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
viewModel: TaskViewModel,
|
||||
onNavigateToAddTask: () -> Unit,
|
||||
onNavigateToSettings: () -> Unit
|
||||
) {
|
||||
val tasks by viewModel.tasks.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"Motívame",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onNavigateToSettings) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Configuración")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = onNavigateToAddTask,
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Agregar tarea")
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
if (tasks.isEmpty()) {
|
||||
EmptyState(modifier = Modifier.padding(paddingValues))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(tasks, key = { it.id }) { task ->
|
||||
TaskCard(
|
||||
task = task,
|
||||
onToggleActive = { viewModel.updateTask(task.copy(isActive = !task.isActive)) },
|
||||
onDelete = { viewModel.deleteTask(task.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TaskCard(
|
||||
task: Task,
|
||||
onToggleActive: () -> Unit,
|
||||
onDelete: () -> Unit
|
||||
) {
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.surfaceVariant,
|
||||
MaterialTheme.colorScheme.surface
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = task.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
Row {
|
||||
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
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { showDeleteDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Eliminar",
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (task.goals.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = "🎯 Metas:",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
task.goals.forEach { goal ->
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Text(
|
||||
text = "•",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = goal,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!task.isActive) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(MaterialTheme.colorScheme.errorContainer)
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "⏸️ Pausada",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showDeleteDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteDialog = false },
|
||||
title = { Text("Eliminar tarea") },
|
||||
text = { Text("¿Estás seguro de que quieres eliminar '${task.title}'?") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDelete()
|
||||
showDeleteDialog = false
|
||||
}
|
||||
) {
|
||||
Text("Eliminar", color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteDialog = false }) {
|
||||
Text("Cancelar")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmptyState(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(100.dp),
|
||||
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "¡Comienza tu viaje!",
|
||||
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",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
251
app/src/main/java/com/manalejandro/motivame/ui/screens/SettingsScreen.kt
Archivo normal
@@ -0,0 +1,251 @@
|
||||
package com.manalejandro.motivame.ui.screens
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.manalejandro.motivame.data.TaskRepository
|
||||
import com.manalejandro.motivame.notifications.NotificationHelper
|
||||
import com.manalejandro.motivame.ui.viewmodel.TaskViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: TaskViewModel,
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val notificationEnabled by viewModel.notificationEnabled.collectAsState()
|
||||
val soundEnabled by viewModel.soundEnabled.collectAsState()
|
||||
val tasks by viewModel.tasks.collectAsState()
|
||||
|
||||
var hasNotificationPermission by remember {
|
||||
mutableStateOf(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
hasNotificationPermission = isGranted
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Configuración") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Volver")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🔔 Notificaciones",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Recordatorios diarios",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "Recibe notificaciones para motivarte",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = notificationEnabled && hasNotificationPermission,
|
||||
onCheckedChange = { enabled ->
|
||||
if (enabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasNotificationPermission) {
|
||||
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
viewModel.toggleNotifications(enabled)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
HorizontalDivider()
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Sonido",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "Reproducir sonido con las notificaciones",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = soundEnabled,
|
||||
onCheckedChange = { viewModel.toggleSound(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🧪 Prueba",
|
||||
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",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasNotificationPermission) {
|
||||
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
scope.launch {
|
||||
val notificationHelper = NotificationHelper(context)
|
||||
notificationHelper.sendMotivationalReminder(tasks, soundEnabled)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = tasks.isNotEmpty()
|
||||
) {
|
||||
Icon(Icons.Default.Notifications, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Enviar notificación de prueba")
|
||||
}
|
||||
|
||||
if (tasks.isEmpty()) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "⚠️ Agrega al menos una tarea para probar las notificaciones",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "ℹ️ Sobre la app",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Motívame v1.0",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
text = "Tu compañero para mantener la motivación en tus tareas diarias",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
28
app/src/main/java/com/manalejandro/motivame/ui/theme/Color.kt
Archivo normal
@@ -0,0 +1,28 @@
|
||||
package com.manalejandro.motivame.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
// Colores principales - tonos vibrantes y motivadores
|
||||
val Primary = Color(0xFF6366F1) // Indigo vibrante
|
||||
val PrimaryDark = Color(0xFF4F46E5)
|
||||
val PrimaryLight = Color(0xFFA5B4FC)
|
||||
|
||||
val Secondary = Color(0xFFEC4899) // Rosa motivador
|
||||
val SecondaryDark = Color(0xFFDB2777)
|
||||
val SecondaryLight = Color(0xFFF9A8D4)
|
||||
|
||||
val Tertiary = Color(0xFF8B5CF6) // Púrpura
|
||||
val Background = Color(0xFFFAFAFA)
|
||||
val Surface = Color(0xFFFFFFFF)
|
||||
|
||||
// Colores para modo oscuro
|
||||
val PrimaryDarkMode = Color(0xFF818CF8)
|
||||
val SecondaryDarkMode = Color(0xFFF472B6)
|
||||
val TertiaryDarkMode = Color(0xFFA78BFA)
|
||||
val BackgroundDark = Color(0xFF121212)
|
||||
val SurfaceDark = Color(0xFF1E1E1E)
|
||||
|
||||
// Colores de acento
|
||||
val Success = Color(0xFF10B981)
|
||||
val Warning = Color(0xFFF59E0B)
|
||||
val Error = Color(0xFFEF4444)
|
||||
81
app/src/main/java/com/manalejandro/motivame/ui/theme/Theme.kt
Archivo normal
@@ -0,0 +1,81 @@
|
||||
package com.manalejandro.motivame.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = PrimaryDarkMode,
|
||||
secondary = SecondaryDarkMode,
|
||||
tertiary = TertiaryDarkMode,
|
||||
background = BackgroundDark,
|
||||
surface = SurfaceDark,
|
||||
error = Error,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFFE0E0E0),
|
||||
onSurface = Color(0xFFE0E0E0)
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Primary,
|
||||
secondary = Secondary,
|
||||
tertiary = Tertiary,
|
||||
background = Background,
|
||||
surface = Surface,
|
||||
error = Error,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1A1A1A),
|
||||
onSurface = Color(0xFF1A1A1A),
|
||||
primaryContainer = PrimaryLight,
|
||||
secondaryContainer = SecondaryLight,
|
||||
onPrimaryContainer = Color(0xFF1E1B4B),
|
||||
onSecondaryContainer = Color(0xFF831843)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MotivameTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = false, // Desactivado para usar nuestro tema personalizado
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
34
app/src/main/java/com/manalejandro/motivame/ui/theme/Type.kt
Archivo normal
@@ -0,0 +1,34 @@
|
||||
package com.manalejandro.motivame.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.manalejandro.motivame.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.manalejandro.motivame.data.Task
|
||||
import com.manalejandro.motivame.data.TaskRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TaskViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val repository = TaskRepository(application)
|
||||
|
||||
private val _tasks = MutableStateFlow<List<Task>>(emptyList())
|
||||
val tasks: StateFlow<List<Task>> = _tasks.asStateFlow()
|
||||
|
||||
private val _notificationEnabled = MutableStateFlow(true)
|
||||
val notificationEnabled: StateFlow<Boolean> = _notificationEnabled.asStateFlow()
|
||||
|
||||
private val _soundEnabled = MutableStateFlow(true)
|
||||
val soundEnabled: StateFlow<Boolean> = _soundEnabled.asStateFlow()
|
||||
|
||||
init {
|
||||
loadTasks()
|
||||
loadSettings()
|
||||
}
|
||||
|
||||
private fun loadTasks() {
|
||||
viewModelScope.launch {
|
||||
repository.tasks.collect { taskList ->
|
||||
_tasks.value = taskList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
viewModelScope.launch {
|
||||
repository.notificationEnabled.collect { enabled ->
|
||||
_notificationEnabled.value = enabled
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
repository.soundEnabled.collect { enabled ->
|
||||
_soundEnabled.value = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTask(task: Task) {
|
||||
viewModelScope.launch {
|
||||
repository.addTask(task)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTask(task: Task) {
|
||||
viewModelScope.launch {
|
||||
repository.updateTask(task)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteTask(taskId: String) {
|
||||
viewModelScope.launch {
|
||||
repository.deleteTask(taskId)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleNotifications(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
repository.setNotificationEnabled(enabled)
|
||||
_notificationEnabled.value = enabled
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleSound(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
repository.setSoundEnabled(enabled)
|
||||
_soundEnabled.value = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.manalejandro.motivame.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.manalejandro.motivame.data.TaskRepository
|
||||
import com.manalejandro.motivame.notifications.NotificationHelper
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
class DailyReminderWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
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 && tasks.isNotEmpty()) {
|
||||
notificationHelper.sendMotivationalReminder(tasks, soundEnabled)
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Archivo normal
@@ -0,0 +1,170 @@
|
||||
<?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>
|
||||
63
app/src/main/res/drawable/ic_launcher_foreground.xml
Archivo normal
@@ -0,0 +1,63 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<group android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
android:translateX="51.2"
|
||||
android:translateY="51.2">
|
||||
<path
|
||||
android:pathData="M144,32L368,32A112,112 0,0 1,480 144L480,368A112,112 0,0 1,368 480L144,480A112,112 0,0 1,32 368L32,144A112,112 0,0 1,144 32z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="32"
|
||||
android:startY="32"
|
||||
android:endX="480"
|
||||
android:endY="480"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF6366F1"/>
|
||||
<item android:offset="1" android:color="#FFEC4899"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M256,256m-160,0a160,160 0,1 1,320 0a160,160 0,1 1,-320 0"
|
||||
android:strokeWidth="16"
|
||||
android:fillColor="#00000000">
|
||||
<aapt:attr name="android:strokeColor">
|
||||
<gradient
|
||||
android:startX="96"
|
||||
android:startY="96"
|
||||
android:endX="416"
|
||||
android:endY="416"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#F2FFFFFF"/>
|
||||
<item android:offset="1" android:color="#CCFFFFFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M256,142L286,220L368,220L302,268L326,346L256,298L186,346L210,268L144,220L226,220Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="144"
|
||||
android:startY="142"
|
||||
android:endX="368"
|
||||
android:endY="346"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#F2FFFFFF"/>
|
||||
<item android:offset="1" android:color="#CCFFFFFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M210,270L242,302L312,230"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="18"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#10B981"
|
||||
android:strokeLineCap="round"/>
|
||||
</group>
|
||||
</vector>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Archivo normal
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Archivo normal
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 3.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 5.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 2.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 3.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 4.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 7.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 7.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 12 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 9.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Archivo normal
|
Después Anchura: | Altura: | Tamaño: 16 KiB |
10
app/src/main/res/values/colors.xml
Archivo normal
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
4
app/src/main/res/values/ic_launcher_background.xml
Archivo normal
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#7B62E2</color>
|
||||
</resources>
|
||||
5
app/src/main/res/values/strings.xml
Archivo normal
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<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>
|
||||
</resources>
|
||||
5
app/src/main/res/values/themes.xml
Archivo normal
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Motivame" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Archivo normal
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older than API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Archivo normal
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
17
app/src/test/java/com/manalejandro/motivame/ExampleUnitTest.kt
Archivo normal
@@ -0,0 +1,17 @@
|
||||
package com.manalejandro.motivame
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||