diff --git a/.gradle/config.properties b/.gradle/config.properties
new file mode 100644
index 0000000..978d1a0
--- /dev/null
+++ b/.gradle/config.properties
@@ -0,0 +1,2 @@
+#Sun Jan 12 11:55:42 COT 2025
+java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..3f50dd0
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,25 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "search_engine",
+ "request": "launch",
+ "type": "dart"
+ },
+ {
+ "name": "search_engine (profile mode)",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "profile"
+ },
+ {
+ "name": "search_engine (release mode)",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "release"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000..e4e6432
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,64 @@
+plugins {
+ id "com.android.application"
+ // START: FlutterFire Configuration
+ id 'com.google.gms.google-services'
+ // END: FlutterFire Configuration
+ id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file("local.properties")
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader("UTF-8") { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
+if (flutterVersionCode == null) {
+ flutterVersionCode = "1"
+}
+
+def flutterVersionName = localProperties.getProperty("flutter.versionName")
+if (flutterVersionName == null) {
+ flutterVersionName = "1.0"
+}
+
+android {
+ namespace = "com.carpa.search_engine"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ lintOptions {
+ disable 'Deprecation'
+ }
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.carpa.search_engine"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdk = 24
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutterVersionCode.toInteger()
+ versionName = flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/android/app/google-services.json b/android/app/google-services.json
new file mode 100644
index 0000000..be95d5f
--- /dev/null
+++ b/android/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "289926488951",
+ "project_id": "carpa-search-engine",
+ "storage_bucket": "carpa-search-engine.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:289926488951:android:ecbbaa7efda3d6721ab772",
+ "android_client_info": {
+ "package_name": "com.carpa.search_engine"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyBwkWdv6iBcKLjAPnoe0WpdpLTO4tBdqSA"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/carpa/carpa/MainActivity.kt b/android/app/src/main/kotlin/com/carpa/carpa/MainActivity.kt
new file mode 100644
index 0000000..7605d4a
--- /dev/null
+++ b/android/app/src/main/kotlin/com/carpa/carpa/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.carpa.carpa
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/android/app/src/main/kotlin/com/carpa/estudiosbiblicosapp/MainActivity.kt b/android/app/src/main/kotlin/com/carpa/estudiosbiblicosapp/MainActivity.kt
new file mode 100644
index 0000000..a9187b9
--- /dev/null
+++ b/android/app/src/main/kotlin/com/carpa/estudiosbiblicosapp/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.carpa.estudiosbiblicosapp
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/android/app/src/main/kotlin/com/carpa/search_engine/MainActivity.kt b/android/app/src/main/kotlin/com/carpa/search_engine/MainActivity.kt
new file mode 100644
index 0000000..9248c91
--- /dev/null
+++ b/android/app/src/main/kotlin/com/carpa/search_engine/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.carpa.search_engine
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..e328483
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,20 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
+
+ext.kotlin_version = '2.1.10'
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..9d3c664
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,29 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "8.1.4" apply false
+ // START: FlutterFire Configuration
+ id "com.google.gms.google-services" version "4.4.2" apply false
+ // END: FlutterFire Configuration
+ id "org.jetbrains.kotlin.android" version "2.1.10" apply false
+}
+
+rootProject.name = 'estudiosbiblicosapp'
+include ":app"
\ No newline at end of file
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-Bold.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-Bold.ttf
new file mode 100644
index 0000000..52b51a8
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-Bold.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-BoldItalic.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-BoldItalic.ttf
new file mode 100644
index 0000000..0f95cf9
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-BoldItalic.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-Italic.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-Italic.ttf
new file mode 100644
index 0000000..2b3f79d
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-Italic.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-Medium.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-Medium.ttf
new file mode 100644
index 0000000..44a931c
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-Medium.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-MediumItalic.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-MediumItalic.ttf
new file mode 100644
index 0000000..fa28334
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-MediumItalic.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-Regular.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-Regular.ttf
new file mode 100644
index 0000000..058901b
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-Regular.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBold.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBold.ttf
new file mode 100644
index 0000000..5f93a1d
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBold.ttf differ
diff --git a/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBoldItalic.ttf b/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBoldItalic.ttf
new file mode 100644
index 0000000..e7b1e7b
Binary files /dev/null and b/assets/fonts/Arial-Narrow/ArchivoNarrow-SemiBoldItalic.ttf differ
diff --git a/assets/fonts/Outfit-Black.ttf b/assets/fonts/Outfit-Black.ttf
new file mode 100644
index 0000000..487752b
Binary files /dev/null and b/assets/fonts/Outfit-Black.ttf differ
diff --git a/assets/fonts/Outfit-Bold.ttf b/assets/fonts/Outfit-Bold.ttf
new file mode 100644
index 0000000..0a081bc
Binary files /dev/null and b/assets/fonts/Outfit-Bold.ttf differ
diff --git a/assets/fonts/Outfit-ExtraBold.ttf b/assets/fonts/Outfit-ExtraBold.ttf
new file mode 100644
index 0000000..0977ed5
Binary files /dev/null and b/assets/fonts/Outfit-ExtraBold.ttf differ
diff --git a/assets/fonts/Outfit-ExtraLight.ttf b/assets/fonts/Outfit-ExtraLight.ttf
new file mode 100644
index 0000000..938fe31
Binary files /dev/null and b/assets/fonts/Outfit-ExtraLight.ttf differ
diff --git a/assets/fonts/Outfit-Light.ttf b/assets/fonts/Outfit-Light.ttf
new file mode 100644
index 0000000..c18b0c1
Binary files /dev/null and b/assets/fonts/Outfit-Light.ttf differ
diff --git a/assets/fonts/Outfit-Medium.ttf b/assets/fonts/Outfit-Medium.ttf
new file mode 100644
index 0000000..7ae796b
Binary files /dev/null and b/assets/fonts/Outfit-Medium.ttf differ
diff --git a/assets/fonts/Outfit-Regular.ttf b/assets/fonts/Outfit-Regular.ttf
new file mode 100644
index 0000000..826899c
Binary files /dev/null and b/assets/fonts/Outfit-Regular.ttf differ
diff --git a/assets/fonts/Outfit-SemiBold.ttf b/assets/fonts/Outfit-SemiBold.ttf
new file mode 100644
index 0000000..6b37eeb
Binary files /dev/null and b/assets/fonts/Outfit-SemiBold.ttf differ
diff --git a/assets/fonts/Outfit-Thin.ttf b/assets/fonts/Outfit-Thin.ttf
new file mode 100644
index 0000000..7d84201
Binary files /dev/null and b/assets/fonts/Outfit-Thin.ttf differ
diff --git a/assets/image/default_thumbnail.jpg b/assets/image/default_thumbnail.jpg
new file mode 100644
index 0000000..29c1406
Binary files /dev/null and b/assets/image/default_thumbnail.jpg differ
diff --git a/assets/image/logo.png b/assets/image/logo.png
new file mode 100644
index 0000000..d5f38b8
Binary files /dev/null and b/assets/image/logo.png differ
diff --git a/assets/lang/en.json b/assets/lang/en.json
new file mode 100644
index 0000000..3390772
--- /dev/null
+++ b/assets/lang/en.json
@@ -0,0 +1,145 @@
+{
+ "title": "Bible\nStudies",
+ "version": "Version",
+ "downloading_data": "Downloading data...",
+ "updating_data": "Updating database...",
+ "success_updating_data": "Data updated successfully",
+ "welcome": "Home",
+ "home": "Bible Studies",
+ "search": "Search",
+ "searching": "Searching",
+ "searching_in_progress": "Searching...",
+ "loading_results": "Loading results...",
+ "loading_more": "Loading more...",
+ "try_different_search": "Try a different search",
+ "search_placeholder": "Search...",
+ "image_saved": "Image saved",
+ "image_saved_desc": "Please, check your gallery",
+ "error_saving_image": "Error saving image",
+ "error_saving_image_desc": "Please, try again later",
+ "results": {
+ "one": "Result",
+ "other": "Results"
+ },
+ "results_found": {
+ "one": "Result found",
+ "other": "Results found"
+ },
+ "calendar": "By date",
+ "empty_search_term": "Please enter a search term",
+ "empty_results": "No results found",
+ "library": "Library",
+ "config": "Settings",
+ "downloaded": "Downloaded",
+ "available": "Available",
+ "about": "About",
+ "settings": "Settings",
+ "lang": "Language",
+ "theme": "Theme",
+ "system": "System",
+ "light": "Light",
+ "dark": "Dark",
+ "auto": "Automatic",
+ "hide_thumbnails": "Hide thumbnails",
+ "download_pdf": "Download PDF",
+ "pdf_download": "Automatic PDF download",
+ "pdf_download_desc": "A PDF will be downloaded automatically if it is not available",
+ "hd_thumbnails_desc": "Only downloaded when entering a specific study",
+ "hd_thumbnails": "High-quality thumbnails",
+ "locale": "Language",
+ "confirm_locale_change": "Confirm language change",
+ "confirm_locale_change_desc": "Are you sure you want to change the language?",
+ "no": "No",
+ "yes": "Yes",
+ "cancel": "Cancel",
+ "accept": "Accept",
+ "last_activities": "Recent activities",
+ "recent_text": "Recent texts",
+ "no_text": "No transcription available",
+ "draft": "Draft",
+ "from": "from",
+ "full_screen": "Full screen",
+ "activity": {
+ "one": "Activity",
+ "other": "Activities"
+ },
+ "back": "Back",
+ "favorite": "Favorite",
+ "year": {
+ "one": "Year",
+ "other": "Years"
+ },
+ "month": {
+ "one": "Month",
+ "other": "Months"
+ },
+ "day": {
+ "one": "Day",
+ "other": "Days"
+ },
+ "hour": {
+ "one": "Hour",
+ "other": "Hours"
+ },
+ "minute": {
+ "one": "Minute",
+ "other": "Minutes"
+ },
+ "filter": "Filter",
+ "clear_filters": "Clear filters",
+ "select_year": "Select year",
+ "select_month": "Select month",
+ "select_year_first": "Select a year first",
+ "no_months_available": "No months available",
+ "no_activities_for_period": "No activities for this period",
+ "clear": "Clear",
+ "apply": "Apply",
+ "cache_storage": "Internal storage",
+ "cache_storage_desc": "Internal storage of the application",
+ "cache_storage_clear": "Clear internal storage",
+ "cache_storage_clear_desc": "Clear the internal storage of the application",
+ "cache_storage_clear_confirm": "Are you sure you want to clear the internal storage of the application?",
+ "cache_storage_clear_success": "Internal storage cleared successfully",
+ "cache_storage_clear_error": "Error clearing internal storage",
+ "storage_usage": "Storage Usage",
+ "storage_usage_desc": "Downloaded files are stored on your device for faster access",
+ "storage_used": "Storage Used",
+ "thumbnails": "Thumbnails",
+ "pdfs": "PDF Documents",
+ "other": "Other Files",
+ "clear_cache": "Clear Cache",
+ "clear_cache_desc": "Are you sure you want to delete all downloaded files? Files will be downloaded again when needed.",
+ "connecting_to_server": "Connecting to server...",
+ "server_unreachable": "Server unreachable",
+ "processing_data": "Processing data...",
+ "processed_items": "Processed {} of {} items",
+ "sync_complete": "Sync completed",
+ "sync_error": "Sync error",
+ "sync_title": "Synchronization",
+ "sync_in_progress": "Synchronization in progress",
+ "open_app": "Open app",
+ "no_new_data": "No new data",
+ "internal": "Internal",
+ "internal_desc": "Internal storage of the application",
+ "internal_clear": "Clear internal storage",
+ "internal_clear_desc": "Clear the internal storage of the application",
+ "internal_clear_confirm": "Are you sure you want to clear the internal storage of the application?",
+ "internal_clear_success": "Internal storage cleared successfully",
+ "internal_clear_error": "Error clearing internal storage",
+ "checking_database": "Checking database...",
+ "syncing_search_index": "Syncing search index...",
+ "updating_search_index": "Updating search index...",
+ "search_index_updated": "Search index updated",
+ "download_manager": "Download Manager",
+ "search_files": "Search files",
+ "select_language": "Select language",
+ "all_languages": "All languages",
+ "all": "All",
+ "images": "Images",
+ "files_found": "files found",
+ "no_files_found": "No downloaded files found",
+ "no_pdfs_found": "No PDF documents found",
+ "confirm_delete": "Confirm deletion",
+ "delete_file_confirmation": "Are you sure you want to delete the file {0}?",
+ "delete": "Delete"
+}
\ No newline at end of file
diff --git a/assets/lang/es.json b/assets/lang/es.json
new file mode 100644
index 0000000..3364ce5
--- /dev/null
+++ b/assets/lang/es.json
@@ -0,0 +1,146 @@
+{
+ "title": "Estudios\nBíblicos",
+ "version": "Versión",
+ "downloading_data": "Descargando datos...",
+ "updating_data": "Actualizando base de datos...",
+ "success_updating_data": "Datos actualizados correctamente",
+ "welcome": "Inicio",
+ "home": "Estudios Bíblicos",
+ "search": "Búsqueda",
+ "searching": "Buscando",
+ "searching_in_progress": "Buscando...",
+ "loading_results": "Cargando resultados...",
+ "loading_more": "Cargando más...",
+ "try_different_search": "Intenta con otra búsqueda",
+ "search_placeholder": "Buscar...",
+ "image_saved": "Imagen guardada",
+ "image_saved_desc": "Por favor, revisa tu galería",
+ "error_saving_image": "Error al guardar la imagen",
+ "error_saving_image_desc": "Por favor, reintenta más tarde",
+ "results": {
+ "one": "Resultado",
+ "other": "Resultados"
+ },
+ "results_found": {
+ "one": "Resultado encontrado",
+ "other": "Resultados encontrados"
+ },
+ "calendar": "Por fecha",
+ "empty_search_term": "Por favor, introduzca un término de búsqueda",
+ "empty_results": "No se encontraron resultados",
+ "library": "Biblioteca",
+ "config": "Ajustes",
+ "downloaded": "Descargados",
+ "available": "Disponibles",
+ "about": "Acerca de",
+ "settings": "Ajustes",
+ "lang": "Idioma",
+ "theme": "Tema",
+ "system": "Sistema",
+ "light": "Claro",
+ "dark": "Oscuro",
+ "auto": "Automático",
+ "hide_thumbnails": "Ocultar portadas",
+ "download_pdf": "Descargar PDF",
+ "pdf_download": "Descarga automática de PDF",
+ "pdf_download_desc": "Al acceder a un PDF no disponible, se descargará automáticamente",
+ "hd_thumbnails_desc": "Sólo se descargarán al entrar a un estudio específico",
+ "hd_thumbnails": "Portadas en alta calidad",
+ "locale": "Idioma",
+ "confirm_locale_change": "Confirmar cambio de idioma",
+ "confirm_locale_change_desc": "¿Está seguro que desea cambiar de idioma?",
+ "no": "No",
+ "yes": "Sí",
+ "cancel": "Cancelar",
+ "accept": "Aceptar",
+ "last_activities": "Últimas actividades",
+ "recent_text": "Textos recientes",
+ "no_text": "No hay transcripción disponible",
+ "draft": "Borrador",
+ "from": "de",
+ "full_screen": "Pantalla completa",
+ "activity": {
+ "one": "Actividad",
+ "other": "Actividades"
+ },
+ "back": "Volver",
+ "favorite": "Favorito",
+ "year": {
+ "one": "Año",
+ "other": "Años"
+ },
+ "month": {
+ "one": "Mes",
+ "other": "Meses"
+ },
+ "day": {
+ "one": "Día",
+ "other": "Días"
+ },
+ "hour": {
+ "one": "Hora",
+ "other": "Horas"
+ },
+ "minute": {
+ "one": "Minuto",
+ "other": "Minutos"
+ },
+ "filter": "Filtrar",
+ "clear_filters": "Limpiar filtros",
+ "select_year": "Seleccionar año",
+ "select_month": "Seleccionar mes",
+ "select_year_first": "Primero selecciona un año",
+ "no_months_available": "No hay meses disponibles",
+ "no_activities_for_period": "No hay actividades para este período",
+ "clear": "Limpiar",
+ "apply": "Aplicar",
+ "cache_storage": "Almacenamiento interno",
+ "cache_storage_desc": "Almacenamiento interno de la aplicación",
+ "cache_storage_clear": "Limpiar almacenamiento interno",
+ "cache_storage_clear_desc": "Limpiar el almacenamiento interno de la aplicación",
+ "cache_storage_clear_confirm": "¿Está seguro que desea limpiar el almacenamiento interno de la aplicación?",
+ "cache_storage_clear_success": "Almacenamiento interno limpiado correctamente",
+ "cache_storage_clear_error": "Error al limpiar el almacenamiento interno",
+ "storage_usage": "Uso de almacenamiento",
+ "storage_usage_desc": "Los archivos descargados se almacenan en tu dispositivo para un acceso más rápido",
+ "storage_used": "Almacenamiento usado",
+ "thumbnails": "Portadas",
+ "pdfs": "Documentos PDF",
+ "other": "Otros archivos",
+ "clear_cache": "Limpiar caché",
+ "clear_cache_desc": "¿Estás seguro de que deseas eliminar todos los archivos descargados? Los archivos se volverán a descargar cuando sean necesarios.",
+ "connecting_to_server": "Conectando al servidor...",
+ "server_unreachable": "Servidor no accesible",
+ "processing_data": "Procesando datos...",
+ "processed_items": "Procesados {} de {} elementos",
+ "sync_complete": "Sincronización completada",
+ "sync_error": "Error de sincronización",
+ "sync_title": "Sincronización",
+ "sync_in_progress": "Sincronización en progreso",
+ "open_app": "Abrir aplicación",
+ "no_new_data": "No hay datos nuevos",
+ "internal": "Interno",
+ "internal_desc": "Almacenamiento interno de la aplicación",
+ "internal_clear": "Limpiar almacenamiento interno",
+ "internal_clear_desc": "Limpiar el almacenamiento interno de la aplicación",
+ "internal_clear_confirm": "¿Está seguro que desea limpiar el almacenamiento interno de la aplicación?",
+ "internal_clear_success": "Almacenamiento interno limpiado correctamente",
+ "internal_clear_error": "Error al limpiar el almacenamiento interno",
+ "checking_database": "Verificando base de datos...",
+ "syncing_search_index": "Sincronizando índice de búsqueda...",
+ "updating_search_index": "Actualizando índice de búsqueda...",
+ "search_index_updated": "Índice de búsqueda actualizado",
+ "download_manager": "Gestor de descargas",
+ "search_files": "Buscar archivos",
+ "select_language": "Seleccionar idioma",
+ "all_languages": "Todos los idiomas",
+ "all": "Todos",
+ "images": "Imágenes",
+ "files_found": "archivos encontrados",
+ "no_files_found": "No se encontraron archivos descargados",
+ "no_pdfs_found": "No se encontraron documentos PDF",
+ "confirm_delete": "Confirmar eliminación",
+ "delete_file_confirmation": "¿Estás seguro de querer eliminar el archivo {0}?",
+ "delete": "Eliminar",
+ "syncronize": "Sincronizar"
+}
\ No newline at end of file
diff --git a/assets/lang/fr.json b/assets/lang/fr.json
new file mode 100644
index 0000000..9c1cd85
--- /dev/null
+++ b/assets/lang/fr.json
@@ -0,0 +1,104 @@
+{
+ "title": "Études\nBibliques",
+ "version": "Version",
+ "downloading_data": "Téléchargement des données...",
+ "updating_data": "Mise à jour de la base de données...",
+ "success_updating_data": "Données mises à jour avec succès",
+ "welcome": "Bienvenue",
+ "home": "Études Biblques",
+ "search": "Recherche",
+ "searching": "Recherche",
+ "searching_in_progress": "Recherche en cours...",
+ "loading_results": "Chargement des résultats...",
+ "loading_more": "Chargement...",
+ "try_different_search": "Essayez une recherche différente",
+ "search_placeholder": "Rechercher...",
+ "image_saved": "Image enregistrée",
+ "image_saved_desc": "Veuillez vérifier votre galerie",
+ "error_saving_image": "Erreur lors de l'enregistrement de l'image",
+ "error_saving_image_desc": "Veuillez réessayer plus tard",
+ "results": {
+ "one": "Résultat",
+ "other": "Résultats"
+ },
+ "results_found": {
+ "one": "Résultat trouvé",
+ "other": "Résultats trouvés"
+ },
+ "calendar": "Par date",
+ "empty_search_term": "Veuillez entrer un terme de recherche",
+ "empty_results": "Aucun résultat trouvé",
+ "library": "Bibliothèque",
+ "config": "Paramètres",
+ "downloaded": "Téléchargés",
+ "available": "Disponibles",
+ "about": "À propos",
+ "settings": "Paramètres",
+ "lang": "Langue",
+ "theme": "Thème",
+ "system": "Système",
+ "light": "Clair",
+ "dark": "Sombre",
+ "auto": "Automatique",
+ "hide_thumbnails": "Cacher les vignettes",
+ "download_pdf": "Télécharger le PDF",
+ "pdf_download": "Téléchargement automatique du PDF",
+ "pdf_download_desc": "Lors de l'accès à un PDF non disponible, il sera téléchargé automatiquement",
+ "hd_thumbnails_desc": "Elles ne seront téléchargées que lors de l'accès à une étude spécifique",
+ "hd_thumbnails": "Vignettes en haute qualité",
+ "locale": "Langue",
+ "confirm_locale_change": "Confirmer le changement de langue",
+ "confirm_locale_change_desc": "Êtes-vous sûr de vouloir changer de langue?",
+ "no": "Non",
+ "yes": "Oui",
+ "cancel": "Annuler",
+ "accept": "Accepter",
+ "last_activities": "Dernières activités",
+ "recent_text": "Textes récents",
+ "no_text": "Aucune transcription disponible",
+ "draft": "Brouillon",
+ "from": "de",
+ "full_screen": "Plein écran",
+ "activity": {
+ "one": "Activité",
+ "other": "Activités"
+ },
+ "back": "Retour",
+ "favorite": "Favori",
+ "year": {
+ "one": "An",
+ "other": "Ans"
+ },
+ "month": {
+ "one": "Mois",
+ "other": "Mois"
+ },
+ "day": {
+ "one": "Jour",
+ "other": "Jours"
+ },
+ "hour": {
+ "one": "Heure",
+ "other": "Heures"
+ },
+ "minute": {
+ "one": "Minute",
+ "other": "Minutes"
+ },
+ "checking_database": "Vérification de la base de données...",
+ "syncing_search_index": "Synchronisation de l'index de recherche...",
+ "updating_search_index": "Mise à jour de l'index de recherche...",
+ "search_index_updated": "Index de recherche mis à jour",
+ "download_manager": "Gestionnaire de téléchargements",
+ "search_files": "Rechercher des fichiers",
+ "select_language": "Sélectionner la langue",
+ "all_languages": "Toutes les langues",
+ "all": "Tous",
+ "images": "Images",
+ "files_found": "fichiers trouvés",
+ "no_files_found": "Aucun fichier téléchargé trouvé",
+ "no_pdfs_found": "Aucun document PDF trouvé",
+ "confirm_delete": "Confirmer la suppression",
+ "delete_file_confirmation": "Êtes-vous sûr de vouloir supprimer le fichier {0}?",
+ "delete": "Supprimer"
+}
diff --git a/assets/lang/pt.json b/assets/lang/pt.json
new file mode 100644
index 0000000..95d7f49
--- /dev/null
+++ b/assets/lang/pt.json
@@ -0,0 +1,104 @@
+{
+ "title": "Estudos\nBíblicos",
+ "version": "Versão",
+ "downloading_data": "Descarregando dados...",
+ "updating_data": "Atualizando base de dados...",
+ "success_updating_data": "Dados atualizados com sucesso",
+ "welcome": "Início",
+ "home": "Estudos Bíblicos",
+ "search": "Pesquisa",
+ "searching": "Pesquisando",
+ "searching_in_progress": "Pesquisando...",
+ "loading_results": "Carregando resultados...",
+ "loading_more": "Carregando mais...",
+ "try_different_search": "Tente uma pesquisa diferente",
+ "search_placeholder": "Pesquisar...",
+ "image_saved": "Imagem salva",
+ "image_saved_desc": "Por favor, verifique sua galeria",
+ "error_saving_image": "Erro ao salvar a imagem",
+ "error_saving_image_desc": "Por favor, tente novamente mais tarde",
+ "results": {
+ "one": "Resultado",
+ "other": "Resultados"
+ },
+ "results_found": {
+ "one": "Resultado encontrado",
+ "other": "Resultados encontrados"
+ },
+ "calendar": "Por data",
+ "empty_search_term": "Por favor, introduza un termo de pesquisa",
+ "empty_results": "Não foram encontrados resultados",
+ "library": "Biblioteca",
+ "config": "Configuração",
+ "downloaded": "Baixados",
+ "available": "Disponíveis",
+ "about": "Sobre",
+ "settings": "Ajustes",
+ "lang": "Idioma",
+ "theme": "Tema",
+ "system": "Sistema",
+ "light": "Claro",
+ "dark": "Escuro",
+ "auto": "Automático",
+ "hide_thumbnails": "Ocultar miniaturas",
+ "download_pdf": "Baixar PDF",
+ "pdf_download": "Baixar automáticamente PDF",
+ "pdf_download_desc": "Ao acessar um PDF não disponivel, sera baixado automáticamente",
+ "hd_thumbnails_desc": "Somente serão baixados ao entrar em um estudo específico",
+ "hd_thumbnails": "Miniaturas em alta qualidade",
+ "locale": "Idioma",
+ "confirm_locale_change": "Conferir mudança de idioma",
+ "confirm_locale_change_desc": "Está seguro que deseja mudar o idioma?",
+ "no": "Não",
+ "yes": "Sim",
+ "cancel": "Cancelar",
+ "accept": "Aceitar",
+ "last_activities": "Últimas atividades",
+ "recent_text": "Textos recentes",
+ "draft": "Rascunho",
+ "no_text": "Nenhuma transcrição disponível",
+ "from": "de",
+ "full_screen": "Tela cheia",
+ "activity": {
+ "one": "Atividade",
+ "other": "Atividades"
+ },
+ "back": "Voltar",
+ "favorite": "Favorito",
+ "year": {
+ "one": "Ano",
+ "other": "Anos"
+ },
+ "month": {
+ "one": "Mes",
+ "other": "Meses"
+ },
+ "day": {
+ "one": "Dia",
+ "other": "Dias"
+ },
+ "hour": {
+ "one": "Hora",
+ "other": "Horas"
+ },
+ "minute": {
+ "one": "Minuto",
+ "other": "Minutos"
+ },
+ "checking_database": "Verificando base de dados...",
+ "syncing_search_index": "Sincronizando índice de pesquisa...",
+ "updating_search_index": "Atualizando índice de pesquisa...",
+ "search_index_updated": "Índice de pesquisa atualizado",
+ "download_manager": "Gerenciador de downloads",
+ "search_files": "Pesquisar arquivos",
+ "select_language": "Selecionar idioma",
+ "all_languages": "Todos os idiomas",
+ "all": "Todos",
+ "images": "Imagens",
+ "files_found": "arquivos encontrados",
+ "no_files_found": "Nenhum arquivo baixado encontrado",
+ "no_pdfs_found": "Nenhum documento PDF encontrado",
+ "confirm_delete": "Confirmar exclusão",
+ "delete_file_confirmation": "Tem certeza que deseja excluir o arquivo {0}?",
+ "delete": "Excluir"
+}
\ No newline at end of file
diff --git a/assets/lang/rw.json b/assets/lang/rw.json
new file mode 100644
index 0000000..a77c2bf
--- /dev/null
+++ b/assets/lang/rw.json
@@ -0,0 +1,105 @@
+{
+ "title": "Amasomo\nYa Bibiliya",
+ "version": "Icyiciro",
+ "downloading_data": "Kurimo gukuramo amakuru...",
+ "updating_data": "Gukurura amakuru mashya...",
+ "success_updating_data": "Amakuru yakuru kugirango",
+ "welcome": "Ahabanza",
+ "home": "Amasomo ya Bibiliya",
+ "search": "Shakisha",
+ "searching": "Kurimo gushakisha",
+ "searching_in_progress": "Kurimo gushakisha...",
+ "loading_results": "Kurimo gutaha ibisubizo...",
+ "loading_more": "Kurimo gutaha ibindi...",
+ "try_different_search": "Gerageza gushakisha mu bundi buryo",
+ "search_placeholder": "Shakisha...",
+ "image_saved": "Imaji yeguruka",
+ "image_saved_desc": "Tafadhali, angalia kwenye galeri",
+ "error_saving_image": "Hitilafu ili kushuka imaji",
+ "error_saving_image_desc": "Tafadhali, jaribu tena baadaye",
+ "results": {
+ "one": "Ibisubizo",
+ "other": "Ibisubizo"
+ },
+ "results_found": {
+ "one": "Ibisubizo byabonetse",
+ "other": "Ibisubizo byabonetse"
+ },
+ "calendar": "Ku itariki",
+ "empty_search_term": "Nyamuneka, shyiramo ijambo ryo gushakisha",
+ "empty_results": "Nta bisubizo byabonetse",
+ "library": "Isomero",
+ "config": "Amashusho",
+ "downloaded": "Kuramo",
+ "available": "Igihe",
+ "about": "Ibyerekeye",
+ "settings": "Amashusho",
+ "lang": "Ururimi",
+ "theme": "Insanganyamatsiko",
+ "system": "Sisitemu",
+ "light": "Urumuri",
+ "dark": "Umwijima",
+ "auto": "Byikora",
+ "hide_thumbnails": "Guhisha uturemereke",
+ "download_pdf": "Kuramo PDF",
+ "pdf_download": "Kuramo PDF byikora",
+ "pdf_download_desc": "Iyo ugeze kuri PDF itaboneka, irahita ikurwamo byikora",
+ "hd_thumbnails_desc": "Bizakururwa gusa igihe winjiye mu isomo ryihariye",
+ "hd_thumbnails": "Uturere two mu bwiza bwo hejuru",
+ "locale": "Ururimi",
+ "confirm_locale_change": "Emeza guhindura ururimi",
+ "confirm_locale_change_desc": "Urashaka guhindura ururimi koko?",
+ "no": "Oya",
+ "yes": "Yego",
+ "cancel": "Gusubika",
+ "accept": "Emera",
+ "last_activities": "Ibikorwa biheruka",
+ "recent_text": "Inyandiko ziheruka",
+ "draft": "Ibyo nkunda",
+ "no_text": "Ibyo nkunda kuri kugirango",
+ "from": "ku",
+ "full_screen": "Ibikorwa byose",
+ "activity": {
+ "one": "Igikorwa",
+ "other": "Ibikorwa"
+ },
+ "back": "Subira inyuma",
+ "favorite": "Ibyo nkunda",
+ "year": {
+ "one": "Umwaka",
+ "other": "Imyaka"
+ },
+ "month": {
+ "one": "Ukwezi",
+ "other": "Amezi"
+ },
+ "day": {
+ "one": "Umunsi",
+ "other": "Iminsi"
+ },
+ "hour": {
+ "one": "Isaha",
+ "other": "Amasaha"
+ },
+ "minute": {
+ "one": "Umunota",
+ "other": "Iminota"
+ },
+ "checking_database": "Kurimo kugenzura ububiko bw'amakuru...",
+ "syncing_search_index": "Kurimo guhuza ibipimo byo gushakisha...",
+ "updating_search_index": "Kurimo kuvugurura ibipimo byo gushakisha...",
+ "search_index_updated": "Ibipimo byo gushakisha byavuguruwe",
+
+ "download_manager": "Umucungamadosiye",
+ "search_files": "Shakisha dosiye",
+ "select_language": "Hitamo ururimi",
+ "all_languages": "Indimi zose",
+ "all": "Byose",
+ "images": "Amashusho",
+ "files_found": "dosiye zabonetse",
+ "no_files_found": "Nta dosiye zakuruwe zabonetse",
+ "no_pdfs_found": "Nta nyandiko za PDF zabonetse",
+ "confirm_delete": "Emeza gukuraho",
+ "delete_file_confirmation": "Uremeza ko ushaka gukuraho dosiye {0}?",
+ "delete": "Kuraho"
+}
diff --git a/assets/svg/logo.svg b/assets/svg/logo.svg
new file mode 100644
index 0000000..fab8c54
--- /dev/null
+++ b/assets/svg/logo.svg
@@ -0,0 +1,33 @@
+
+
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/flutter_native_splash.yaml b/flutter_native_splash.yaml
new file mode 100644
index 0000000..88d1182
--- /dev/null
+++ b/flutter_native_splash.yaml
@@ -0,0 +1,144 @@
+flutter_native_splash:
+ # This package generates native code to customize Flutter's default white native splash screen
+ # with background color and splash image.
+ # Customize the parameters below, and run the following command in the terminal:
+ # dart run flutter_native_splash:create
+ # To restore Flutter's default white splash screen, run the following command in the terminal:
+ # dart run flutter_native_splash:remove
+
+ # IMPORTANT NOTE: These parameter do not affect the configuration of Android 12 and later, which
+ # handle splash screens differently that prior versions of Android. Android 12 and later must be
+ # configured specifically in the android_12 section below.
+
+ # color or background_image is the only required parameter. Use color to set the background
+ # of your splash screen to a solid color. Use background_image to set the background of your
+ # splash screen to a png image. This is useful for gradients. The image will be stretch to the
+ # size of the app. Only one parameter can be used, color and background_image cannot both be set.
+ color: "#AFC289"
+ #background_image: "assets/background.png"
+
+ # Optional parameters are listed below. To enable a parameter, uncomment the line by removing
+ # the leading # character.
+
+ # The image parameter allows you to specify an image used in the splash screen. It must be a
+ # png file and should be sized for 4x pixel density.
+ image: assets/image/logo.png
+
+ # The branding property allows you to specify an image used as branding in the splash screen.
+ # It must be a png file. It is supported for Android, iOS and the Web. For Android 12,
+ # see the Android 12 section below.
+ #branding: assets/dart.png
+
+ # To position the branding image at the bottom of the screen you can use bottom, bottomRight,
+ # and bottomLeft. The default values is bottom if not specified or specified something else.
+ #branding_mode: bottom
+
+ # The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
+ # and image when the device is in dark mode. If they are not specified, the app will use the
+ # parameters from above. If the image_dark parameter is specified, color_dark or
+ # background_image_dark must be specified. color_dark and background_image_dark cannot both be
+ # set.
+ #color_dark: "#042a49"
+ #background_image_dark: "assets/dark-background.png"
+ #image_dark: assets/splash-invert.png
+ #branding_dark: assets/dart_dark.png
+
+ # From Android 12 onwards, the splash screen is handled differently than in previous versions.
+ # Please visit https://developer.android.com/guide/topics/ui/splash-screen
+ # Following are specific parameters for Android 12+.
+ android_12:
+ # The image parameter sets the splash screen icon image. If this parameter is not specified,
+ # the app's launcher icon will be used instead.
+ # Please note that the splash screen will be clipped to a circle on the center of the screen.
+ # App icon with an icon background: This should be 960×960 pixels, and fit within a circle
+ # 640 pixels in diameter.
+ # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
+ # 768 pixels in diameter.
+ #image: assets/android12splash.png
+
+ # Splash screen background color.
+ color: "#AFC289"
+
+ # App icon background color.
+ #icon_background_color: "#111111"
+
+ # The branding property allows you to specify an image used as branding in the splash screen.
+ #branding: assets/dart.png
+
+ # The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
+ # apply when the device is in dark mode. If they are not specified, the app will use the
+ # parameters from above.
+ #image_dark: assets/android12splash-invert.png
+ #color_dark: "#042a49"
+ #icon_background_color_dark: "#eeeeee"
+
+ # The android, ios and web parameters can be used to disable generating a splash screen on a given
+ # platform.
+ #android: false
+ #ios: false
+ #web: false
+
+ # Platform specific images can be specified with the following parameters, which will override
+ # the respective parameter. You may specify all, selected, or none of these parameters:
+ #color_android: "#42a5f5"
+ #color_dark_android: "#042a49"
+ #color_ios: "#42a5f5"
+ #color_dark_ios: "#042a49"
+ #color_web: "#42a5f5"
+ #color_dark_web: "#042a49"
+ #image_android: assets/splash-android.png
+ #image_dark_android: assets/splash-invert-android.png
+ #image_ios: assets/splash-ios.png
+ #image_dark_ios: assets/splash-invert-ios.png
+ #image_web: assets/splash-web.gif
+ #image_dark_web: assets/splash-invert-web.gif
+ #background_image_android: "assets/background-android.png"
+ #background_image_dark_android: "assets/dark-background-android.png"
+ #background_image_ios: "assets/background-ios.png"
+ #background_image_dark_ios: "assets/dark-background-ios.png"
+ #background_image_web: "assets/background-web.png"
+ #background_image_dark_web: "assets/dark-background-web.png"
+ #branding_android: assets/brand-android.png
+ #branding_dark_android: assets/dart_dark-android.png
+ #branding_ios: assets/brand-ios.png
+ #branding_dark_ios: assets/dart_dark-ios.png
+ #branding_web: assets/brand-web.gif
+ #branding_dark_web: assets/dart_dark-web.gif
+
+ # The position of the splash image can be set with android_gravity, ios_content_mode, and
+ # web_image_mode parameters. All default to center.
+ #
+ # android_gravity can be one of the following Android Gravity (see
+ # https://developer.android.com/reference/android/view/Gravity): bottom, center,
+ # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
+ # fill_vertical, left, right, start, or top.
+ #android_gravity: center
+ #
+ # ios_content_mode can be one of the following iOS UIView.ContentMode (see
+ # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
+ # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
+ # bottomLeft, or bottomRight.
+ #ios_content_mode: center
+ #
+ # web_image_mode can be one of the following modes: center, contain, stretch, and cover.
+ #web_image_mode: center
+
+ # The screen orientation can be set in Android with the android_screen_orientation parameter.
+ # Valid parameters can be found here:
+ # https://developer.android.com/guide/topics/manifest/activity-element#screen
+ #android_screen_orientation: sensorLandscape
+
+ # To hide the notification bar, use the fullscreen parameter. Has no effect in web since web
+ # has no notification bar. Defaults to false.
+ # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
+ # To show the notification bar, add the following code to your Flutter app:
+ # WidgetsFlutterBinding.ensureInitialized();
+ # SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top], );
+ #fullscreen: true
+
+ # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
+ # with the info_plist_files parameter. Remove only the # characters in the three lines below,
+ # do not remove any spaces:
+ #info_plist_files:
+ # - 'ios/Runner/Info-Debug.plist'
+ # - 'ios/Runner/Info-Release.plist'
\ No newline at end of file
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..42055d9
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,98 @@
+# Uncomment this line to define a global platform for your project
+platform :ios, '12.0' # Versión mínima para la aplicación principal
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+# Agregar el target de la extensión solo si existe
+extension_path = Dir.glob("**/*SearchWidgetExtension.xcodeproj").first || Dir.glob("**/SearchWidgetExtension/SearchWidgetExtension.xcodeproj").first
+if extension_path != nil
+ puts "Encontrada la extensión SearchWidgetExtension, configurando pods..."
+ target 'SearchWidgetExtension' do
+ use_frameworks!
+ use_modular_headers!
+ # Remove SwiftUI pod as it's part of the iOS SDK
+ # pod 'SwiftUI' # Framework básico para SwiftUI
+
+ # Don't share the same pods as the main app to avoid circular dependencies
+ # Add only specific pods needed by the extension
+ end
+else
+ puts "No se encontró la extensión SearchWidgetExtension, omitiendo configuración específica."
+end
+
+post_install do |installer|
+ # Configurar iOS 12.0 como el mínimo por defecto, pero dejar que cada pod use su propio mínimo
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ # Asegurarse de que ningún pod tenga deployment target menor que 12.0
+ if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 12.0
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
+ end
+ end
+ end
+
+ # Configurar los targets de Flutter
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+
+ target.build_configurations.each do |config|
+ # Configuraciones específicas para cada target
+ if target.name == 'Runner'
+ # Configuraciones para el target principal
+ config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
+ config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
+ config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
+ end
+
+ # Configuraciones para la extensión si existe
+ if target.name.include?('SearchWidgetExtension')
+ config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
+ config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
+ config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
+ config.build_settings['SKIP_INSTALL'] = 'YES'
+ # Establecer iOS 16.1 como mínimo solo para la extensión (requisito para Live Activities)
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.1'
+ end
+ end
+ end
+
+ # Corregir problemas con el toolchain
+ installer.generated_projects.each do |project|
+ project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['DEVELOPMENT_TEAM'] = ''
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 0000000..0f43270
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,164 @@
+PODS:
+ - audio_session (0.0.1):
+ - Flutter
+ - country_codes (0.0.1):
+ - Flutter
+ - device_info_plus (0.0.1):
+ - Flutter
+ - Flutter (1.0.0)
+ - flutter_app_group_directory (0.0.1):
+ - Flutter
+ - flutter_local_notifications (0.0.1):
+ - Flutter
+ - flutter_mimir (0.0.1)
+ - flutter_native_splash (2.4.3):
+ - Flutter
+ - gal (1.0.0):
+ - Flutter
+ - FlutterMacOS
+ - just_audio (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - live_activities (0.0.1):
+ - Flutter
+ - package_info_plus (0.4.5):
+ - Flutter
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - sqflite_darwin (0.0.4):
+ - Flutter
+ - FlutterMacOS
+ - sqlite3 (3.49.1):
+ - sqlite3/common (= 3.49.1)
+ - sqlite3/common (3.49.1)
+ - sqlite3/dbstatvtab (3.49.1):
+ - sqlite3/common
+ - sqlite3/fts5 (3.49.1):
+ - sqlite3/common
+ - sqlite3/perf-threadsafe (3.49.1):
+ - sqlite3/common
+ - sqlite3/rtree (3.49.1):
+ - sqlite3/common
+ - sqlite3_flutter_libs (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - sqlite3 (~> 3.49.1)
+ - sqlite3/dbstatvtab
+ - sqlite3/fts5
+ - sqlite3/perf-threadsafe
+ - sqlite3/rtree
+ - syncfusion_flutter_pdfviewer (0.0.1):
+ - Flutter
+ - url_launcher_ios (0.0.1):
+ - Flutter
+ - video_player_avfoundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - wakelock_plus (0.0.1):
+ - Flutter
+ - webview_flutter_wkwebview (0.0.1):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - audio_session (from `.symlinks/plugins/audio_session/ios`)
+ - country_codes (from `.symlinks/plugins/country_codes/ios`)
+ - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
+ - Flutter (from `Flutter`)
+ - flutter_app_group_directory (from `.symlinks/plugins/flutter_app_group_directory/ios`)
+ - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
+ - flutter_mimir (from `.symlinks/plugins/flutter_mimir/ios`)
+ - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
+ - gal (from `.symlinks/plugins/gal/darwin`)
+ - just_audio (from `.symlinks/plugins/just_audio/darwin`)
+ - live_activities (from `.symlinks/plugins/live_activities/ios`)
+ - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+ - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
+ - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
+ - syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
+ - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+ - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
+ - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
+ - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
+
+SPEC REPOS:
+ trunk:
+ - sqlite3
+
+EXTERNAL SOURCES:
+ audio_session:
+ :path: ".symlinks/plugins/audio_session/ios"
+ country_codes:
+ :path: ".symlinks/plugins/country_codes/ios"
+ device_info_plus:
+ :path: ".symlinks/plugins/device_info_plus/ios"
+ Flutter:
+ :path: Flutter
+ flutter_app_group_directory:
+ :path: ".symlinks/plugins/flutter_app_group_directory/ios"
+ flutter_local_notifications:
+ :path: ".symlinks/plugins/flutter_local_notifications/ios"
+ flutter_mimir:
+ :path: ".symlinks/plugins/flutter_mimir/ios"
+ flutter_native_splash:
+ :path: ".symlinks/plugins/flutter_native_splash/ios"
+ gal:
+ :path: ".symlinks/plugins/gal/darwin"
+ just_audio:
+ :path: ".symlinks/plugins/just_audio/darwin"
+ live_activities:
+ :path: ".symlinks/plugins/live_activities/ios"
+ package_info_plus:
+ :path: ".symlinks/plugins/package_info_plus/ios"
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ sqflite_darwin:
+ :path: ".symlinks/plugins/sqflite_darwin/darwin"
+ sqlite3_flutter_libs:
+ :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
+ syncfusion_flutter_pdfviewer:
+ :path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
+ url_launcher_ios:
+ :path: ".symlinks/plugins/url_launcher_ios/ios"
+ video_player_avfoundation:
+ :path: ".symlinks/plugins/video_player_avfoundation/darwin"
+ wakelock_plus:
+ :path: ".symlinks/plugins/wakelock_plus/ios"
+ webview_flutter_wkwebview:
+ :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
+
+SPEC CHECKSUMS:
+ audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
+ country_codes: b534fe92b5dd4d4cfd31a720f2bfa223373d162c
+ device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ flutter_app_group_directory: 55b5362007d1c0cb45dc1dd1e94f67d615f45a6b
+ flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
+ flutter_mimir: abc5575f7deea72a2716fb3a75295984681dfd87
+ flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
+ gal: baecd024ebfd13c441269ca7404792a7152fde89
+ just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
+ live_activities: f2e133059358f99655c8d181d65ff54f024a6e93
+ package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
+ path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
+ sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
+ sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
+ sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241
+ syncfusion_flutter_pdfviewer: dfb514751af5b6b71e504c9c04a2e4ddbc1dd895
+ url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
+ video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
+ wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
+ webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
+
+PODFILE CHECKSUM: c6b83d8cd699251ccc3bbb754fede6c1135cc1f5
+
+COCOAPODS: 1.16.2
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png
new file mode 100644
index 0000000..08c3059
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
new file mode 100644
index 0000000..ebcedef
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
new file mode 100644
index 0000000..457f39d
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
new file mode 100644
index 0000000..6f6897f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png
new file mode 100644
index 0000000..8bb5efb
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png
new file mode 100644
index 0000000..0a4519f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png
new file mode 100644
index 0000000..898535c
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png
new file mode 100644
index 0000000..5f3753f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png
new file mode 100644
index 0000000..5d6ff05
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
new file mode 100644
index 0000000..a55bc4e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png
new file mode 100644
index 0000000..d06312b
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png
new file mode 100644
index 0000000..699f3d2
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
new file mode 100644
index 0000000..6cf2f54
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png
new file mode 100644
index 0000000..b9912cf
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
new file mode 100644
index 0000000..64665ec
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png
new file mode 100644
index 0000000..05da52b
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png
new file mode 100644
index 0000000..9b90cb6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
new file mode 100644
index 0000000..ace1ec2
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
new file mode 100644
index 0000000..e7efc4f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
new file mode 100644
index 0000000..007d733
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png
new file mode 100644
index 0000000..f561a85
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png
new file mode 100644
index 0000000..63bf7bb
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png
new file mode 100644
index 0000000..144a28d
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
new file mode 100644
index 0000000..8bd503b
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
new file mode 100644
index 0000000..77ce450
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
new file mode 100644
index 0000000..7d60b3a
--- /dev/null
+++ b/ios/Runner/Runner.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.lgcc.search
+
+
+
diff --git a/ios/SearchWidget/AppIntent.swift b/ios/SearchWidget/AppIntent.swift
new file mode 100644
index 0000000..06a68cb
--- /dev/null
+++ b/ios/SearchWidget/AppIntent.swift
@@ -0,0 +1,18 @@
+//
+// AppIntent.swift
+// SearchWidget
+//
+// Created by Johann Villegas on 9/04/25.
+//
+
+import WidgetKit
+import AppIntents
+
+struct ConfigurationAppIntent: WidgetConfigurationIntent {
+ static var title: LocalizedStringResource { "Configuration" }
+ static var description: IntentDescription { "This is an example widget." }
+
+ // An example configurable parameter.
+ @Parameter(title: "Favorite Emoji", default: "😃")
+ var favoriteEmoji: String
+}
diff --git a/ios/SearchWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/ios/SearchWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/ios/SearchWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/SearchWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/SearchWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..2305880
--- /dev/null
+++ b/ios/SearchWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/SearchWidget/Assets.xcassets/Contents.json b/ios/SearchWidget/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/ios/SearchWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/SearchWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/ios/SearchWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/ios/SearchWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/SearchWidget/Info.plist b/ios/SearchWidget/Info.plist
new file mode 100644
index 0000000..04f07b5
--- /dev/null
+++ b/ios/SearchWidget/Info.plist
@@ -0,0 +1,13 @@
+
+
+
+
+ NSSupportsLiveActivities
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/ios/SearchWidget/SearchWidget.swift b/ios/SearchWidget/SearchWidget.swift
new file mode 100644
index 0000000..75b0068
--- /dev/null
+++ b/ios/SearchWidget/SearchWidget.swift
@@ -0,0 +1,226 @@
+//
+// SearchWidget.swift
+// SearchWidget
+//
+// Created by Johann Villegas on 9/04/25.
+//
+
+import WidgetKit
+import SwiftUI
+import ActivityKit
+
+// Shared FlutterLiveActivities definition for use in both app and widget extension
+struct FlutterLiveActivities: ActivityAttributes, Identifiable {
+ public typealias ContentState = FlutterLiveActivitiesContent
+
+ public struct FlutterLiveActivitiesContent: Codable, Hashable {
+ var title: String
+ var query: String
+ var resultsCount: String
+ var timestamp: String
+ }
+
+ var id = UUID()
+}
+
+// Define the lock screen/banner state of Live Activities
+@available(iOS 16.2, *)
+struct FlutterLiveActivitiesLiveActivity: Widget {
+ var body: some WidgetConfiguration {
+ ActivityConfiguration(for: FlutterLiveActivities.self) { context in
+ // Lock screen/banner UI goes here
+ LockScreenLiveActivityView(context: context)
+ } dynamicIsland: { context in
+ // Dynamic Island UI goes here
+ DynamicIslandLiveActivityView(context: context)
+ }
+ }
+}
+
+@available(iOS 16.2, *)
+struct LockScreenLiveActivityView: View {
+ let context: ActivityViewContext
+
+ var body: some View {
+ VStack {
+ Text(context.state.title)
+ .font(.headline)
+
+ HStack {
+ Image(systemName: "magnifyingglass")
+ .foregroundColor(.green)
+ Text(context.state.query)
+ .font(.subheadline)
+ }
+
+ HStack {
+ Image(systemName: "doc.text")
+ .foregroundColor(.blue)
+ Text("Results: \(context.state.resultsCount)")
+ .font(.subheadline)
+ }
+
+ Text("Updated: \(formattedDate(from: context.state.timestamp))")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ }
+ .padding()
+ }
+
+ func formattedDate(from isoString: String) -> String {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
+ if let date = dateFormatter.date(from: isoString) {
+ dateFormatter.dateFormat = "HH:mm:ss"
+ return dateFormatter.string(from: date)
+ }
+ return isoString
+ }
+}
+
+@available(iOS 16.2, *)
+struct DynamicIslandLiveActivityView: View {
+ let context: ActivityViewContext
+
+ var body: some View {
+ DynamicIsland {
+ // Expanded state
+ DynamicIslandExpandedRegion(.leading) {
+ HStack {
+ Image(systemName: "magnifyingglass")
+ .foregroundColor(.green)
+ Text(context.state.query)
+ .font(.headline)
+ .lineLimit(1)
+ }
+ }
+
+ DynamicIslandExpandedRegion(.trailing) {
+ HStack {
+ Image(systemName: "doc.text")
+ .foregroundColor(.blue)
+ Text(context.state.resultsCount)
+ .font(.headline)
+ }
+ }
+
+ DynamicIslandExpandedRegion(.bottom) {
+ VStack(alignment: .leading) {
+ Text(context.state.title)
+ .font(.caption)
+ Text("Updated: \(formattedDate(from: context.state.timestamp))")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ }
+ }
+ } compactLeading: {
+ Image(systemName: "magnifyingglass")
+ .foregroundColor(.green)
+ } compactTrailing: {
+ Text(context.state.resultsCount)
+ .font(.headline)
+ } minimal: {
+ Image(systemName: "magnifyingglass")
+ .foregroundColor(.green)
+ }
+ }
+
+ func formattedDate(from isoString: String) -> String {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
+ if let date = dateFormatter.date(from: isoString) {
+ dateFormatter.dateFormat = "HH:mm:ss"
+ return dateFormatter.string(from: date)
+ }
+ return isoString
+ }
+}
+
+struct SearchWidget: Widget {
+ let kind: String = "SearchWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: EmptyTimelineProvider()) { _ in
+ EmptyWidgetView()
+ }
+ .configurationDisplayName("Estudios Bíblicos")
+ .description("Search Widget for Bible Studies App")
+ .supportedFamilies([.systemSmall, .systemMedium])
+ }
+}
+
+// Empty widget for devices that don't support Live Activities
+struct EmptyWidgetView: View {
+ var body: some View {
+ ZStack {
+ Color(red: 0.42, green: 0.55, blue: 0.14) // #6b8e23 color
+ VStack {
+ Image(systemName: "magnifyingglass")
+ .resizable()
+ .scaledToFit()
+ .frame(width: 40, height: 40)
+ .foregroundColor(.white)
+ Text("Estudios Bíblicos")
+ .font(.headline)
+ .foregroundColor(.white)
+ }
+ .padding()
+ }
+ }
+}
+
+// Empty provider for devices that don't support Live Activities
+struct EmptyTimelineProvider: TimelineProvider {
+ func placeholder(in context: Context) -> EmptyTimelineEntry {
+ EmptyTimelineEntry()
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (EmptyTimelineEntry) -> ()) {
+ let entry = EmptyTimelineEntry()
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
+ let entry = EmptyTimelineEntry()
+ let timeline = Timeline(entries: [entry], policy: .never)
+ completion(timeline)
+ }
+}
+
+struct EmptyTimelineEntry: TimelineEntry {
+ let date: Date = Date()
+}
+
+struct SearchWidget_Previews: PreviewProvider {
+ static var previews: some View {
+ SearchWidget()
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+ }
+}
+
+// Preview for Live Activities
+@available(iOS 16.2, *)
+struct FlutterLiveActivitiesLiveActivity_Previews: PreviewProvider {
+ static let attributes = FlutterLiveActivities()
+ static let contentState = FlutterLiveActivities.ContentState(
+ title: "Searching Bible Studies",
+ query: "resurrection",
+ resultsCount: "42",
+ timestamp: ISO8601DateFormatter().string(from: Date())
+ )
+
+ static var previews: some View {
+ attributes
+ .previewContext(contentState, viewKind: .dynamicIsland(.compact))
+ .previewDisplayName("Island Compact")
+ attributes
+ .previewContext(contentState, viewKind: .dynamicIsland(.expanded))
+ .previewDisplayName("Island Expanded")
+ attributes
+ .previewContext(contentState, viewKind: .dynamicIsland(.minimal))
+ .previewDisplayName("Minimal")
+ attributes
+ .previewContext(contentState, viewKind: .content)
+ .previewDisplayName("Notification")
+ }
+}
diff --git a/ios/SearchWidget/SearchWidgetBundle.swift b/ios/SearchWidget/SearchWidgetBundle.swift
new file mode 100644
index 0000000..c3ff080
--- /dev/null
+++ b/ios/SearchWidget/SearchWidgetBundle.swift
@@ -0,0 +1,19 @@
+//
+// SearchWidgetBundle.swift
+// SearchWidget
+//
+// Created by Johann Villegas on 9/04/25.
+//
+
+import WidgetKit
+import SwiftUI
+
+@main
+struct SearchWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ SearchWidget()
+ if #available(iOS 16.2, *) {
+ FlutterLiveActivitiesLiveActivity()
+ }
+ }
+}
diff --git a/ios/SearchWidget/SearchWidgetControl.swift b/ios/SearchWidget/SearchWidgetControl.swift
new file mode 100644
index 0000000..d5e26d5
--- /dev/null
+++ b/ios/SearchWidget/SearchWidgetControl.swift
@@ -0,0 +1,77 @@
+//
+// SearchWidgetControl.swift
+// SearchWidget
+//
+// Created by Johann Villegas on 9/04/25.
+//
+
+import AppIntents
+import SwiftUI
+import WidgetKit
+
+struct SearchWidgetControl: ControlWidget {
+ static let kind: String = "com.carpa.searchEngine.SearchWidget"
+
+ var body: some ControlWidgetConfiguration {
+ AppIntentControlConfiguration(
+ kind: Self.kind,
+ provider: Provider()
+ ) { value in
+ ControlWidgetToggle(
+ "Start Timer",
+ isOn: value.isRunning,
+ action: StartTimerIntent(value.name)
+ ) { isRunning in
+ Label(isRunning ? "On" : "Off", systemImage: "timer")
+ }
+ }
+ .displayName("Timer")
+ .description("A an example control that runs a timer.")
+ }
+}
+
+extension SearchWidgetControl {
+ struct Value {
+ var isRunning: Bool
+ var name: String
+ }
+
+ struct Provider: AppIntentControlValueProvider {
+ func previewValue(configuration: TimerConfiguration) -> Value {
+ SearchWidgetControl.Value(isRunning: false, name: configuration.timerName)
+ }
+
+ func currentValue(configuration: TimerConfiguration) async throws -> Value {
+ let isRunning = true // Check if the timer is running
+ return SearchWidgetControl.Value(isRunning: isRunning, name: configuration.timerName)
+ }
+ }
+}
+
+struct TimerConfiguration: ControlConfigurationIntent {
+ static let title: LocalizedStringResource = "Timer Name Configuration"
+
+ @Parameter(title: "Timer Name", default: "Timer")
+ var timerName: String
+}
+
+struct StartTimerIntent: SetValueIntent {
+ static let title: LocalizedStringResource = "Start a timer"
+
+ @Parameter(title: "Timer Name")
+ var name: String
+
+ @Parameter(title: "Timer is running")
+ var value: Bool
+
+ init() {}
+
+ init(_ name: String) {
+ self.name = name
+ }
+
+ func perform() async throws -> some IntentResult {
+ // Start the timer…
+ return .result()
+ }
+}
diff --git a/ios/SearchWidget/SearchWidgetLiveActivity.swift b/ios/SearchWidget/SearchWidgetLiveActivity.swift
new file mode 100644
index 0000000..210a66a
--- /dev/null
+++ b/ios/SearchWidget/SearchWidgetLiveActivity.swift
@@ -0,0 +1,80 @@
+//
+// SearchWidgetLiveActivity.swift
+// SearchWidget
+//
+// Created by Johann Villegas on 9/04/25.
+//
+
+import ActivityKit
+import WidgetKit
+import SwiftUI
+
+struct SearchWidgetAttributes: ActivityAttributes {
+ public typealias LiveDeliveryData = ContentState
+
+ public struct ContentState: Codable, Hashable {
+ var title: String
+ var body: String
+ var searchTerm: String
+ var totalResults: Int
+ var timestamp: Int64
+ }
+
+ var id = UUID()
+}
+
+struct SearchWidgetLiveActivity: Widget {
+ var body: some WidgetConfiguration {
+ ActivityConfiguration(for: SearchWidgetAttributes.self) { context in
+ // Lock Screen/Banner UI
+ VStack {
+ HStack {
+ Text(context.state.title)
+ .font(.headline)
+ Spacer()
+ Text("\(context.state.totalResults)")
+ .font(.caption)
+ }
+ Text(context.state.body)
+ .font(.subheadline)
+ if !context.state.searchTerm.isEmpty {
+ Text("Search: \(context.state.searchTerm)")
+ .font(.caption)
+ }
+ }
+ .padding()
+ } dynamicIsland: { context in
+ // Dynamic Island UI
+ DynamicIsland {
+ // Expanded UI
+ DynamicIslandExpandedRegion(.leading) {
+ Text(context.state.title)
+ .font(.headline)
+ }
+
+ DynamicIslandExpandedRegion(.trailing) {
+ Text("\(context.state.totalResults)")
+ .font(.caption)
+ }
+
+ DynamicIslandExpandedRegion(.bottom) {
+ Text(context.state.body)
+ .font(.subheadline)
+ if !context.state.searchTerm.isEmpty {
+ Text("Search: \(context.state.searchTerm)")
+ .font(.caption)
+ }
+ }
+ } compactLeading: {
+ // Compact Leading
+ Text("\(context.state.totalResults)")
+ } compactTrailing: {
+ // Compact Trailing
+ Image(systemName: "magnifyingglass")
+ } minimal: {
+ // Minimal UI
+ Image(systemName: "magnifyingglass")
+ }
+ }
+ }
+}
diff --git a/ios/SearchWidgetExtension.entitlements b/ios/SearchWidgetExtension.entitlements
new file mode 100644
index 0000000..6d193a1
--- /dev/null
+++ b/ios/SearchWidgetExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.carpa.searchEngine.widget
+
+
+
diff --git a/lib/controllers/notification_controller.dart b/lib/controllers/notification_controller.dart
new file mode 100644
index 0000000..c9338e8
--- /dev/null
+++ b/lib/controllers/notification_controller.dart
@@ -0,0 +1,85 @@
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:flutter/material.dart';
+
+class NotificationController {
+ static final FlutterLocalNotificationsPlugin _notifications =
+ FlutterLocalNotificationsPlugin();
+
+ static Future initialize() async {
+ const androidSettings =
+ AndroidInitializationSettings('@mipmap/ic_launcher');
+ const iosSettings = DarwinInitializationSettings(
+ requestAlertPermission: true,
+ requestBadgePermission: true,
+ requestSoundPermission: true,
+ );
+
+ const initSettings = InitializationSettings(
+ android: androidSettings,
+ iOS: iosSettings,
+ );
+
+ await _notifications.initialize(
+ initSettings,
+ onDidReceiveNotificationResponse: (NotificationResponse response) {
+ if (response.payload != null) {
+ // Handle notification tap
+ // You can navigate or perform actions based on the payload
+ }
+ },
+ );
+ }
+
+ static Future showNotification({
+ required String title,
+ required String body,
+ String? payload,
+ }) async {
+ const androidDetails = AndroidNotificationDetails(
+ 'basic_channel',
+ 'Basic notifications',
+ channelDescription: 'Notification channel for basic tests',
+ importance: Importance.high,
+ priority: Priority.high,
+ color: Color(0xFF9D50DD),
+ enableLights: true,
+ enableVibration: true,
+ playSound: true,
+ );
+
+ const iosDetails = DarwinNotificationDetails(
+ presentAlert: true,
+ presentBadge: true,
+ presentSound: true,
+ );
+
+ const details = NotificationDetails(
+ android: androidDetails,
+ iOS: iosDetails,
+ );
+
+ await _notifications.show(
+ 0,
+ title,
+ body,
+ details,
+ payload: payload,
+ );
+ }
+
+ static Future requestPermissions() async {
+ await _notifications
+ .resolvePlatformSpecificImplementation<
+ AndroidFlutterLocalNotificationsPlugin>()
+ ?.requestNotificationsPermission();
+
+ await _notifications
+ .resolvePlatformSpecificImplementation<
+ IOSFlutterLocalNotificationsPlugin>()
+ ?.requestPermissions(
+ alert: true,
+ badge: true,
+ sound: true,
+ );
+ }
+}
diff --git a/lib/database.dart b/lib/database.dart
new file mode 100644
index 0000000..0870144
--- /dev/null
+++ b/lib/database.dart
@@ -0,0 +1,826 @@
+import 'dart:io';
+import 'package:flutter/foundation.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:path/path.dart' as p;
+import 'package:drift/drift.dart';
+import 'package:drift/native.dart';
+import 'package:drift/drift.dart' as drift;
+import 'package:search_engine/services/config_service.dart';
+import 'package:search_engine/services/mimir_service.dart';
+
+part 'database.g.dart';
+
+class Draft {
+ final String id;
+ final String title;
+ final DateTime date;
+ final int activity;
+ final String thumbnail;
+ final int draft;
+ final String locale;
+ final String country;
+ final String city;
+ final String? body;
+ final String? pdf;
+ final String languagesCode;
+ final Map? searchResultData;
+ final int position;
+ final int length;
+
+ Draft({
+ required this.id,
+ required this.title,
+ required this.date,
+ required this.activity,
+ required this.thumbnail,
+ required this.draft,
+ required this.locale,
+ required this.country,
+ required this.city,
+ this.body,
+ this.pdf,
+ required this.languagesCode,
+ this.searchResultData,
+ this.position = 0,
+ this.length = 0,
+ });
+}
+
+class Messages extends Table {
+ TextColumn get id => text()();
+
+ TextColumn get country => text()();
+
+ TextColumn get city => text()();
+
+ DateTimeColumn get date => dateTime()();
+
+ IntColumn get activity => integer()();
+
+ IntColumn get draft => integer()();
+
+ TextColumn get thumbnail => text()();
+
+ @override
+ Set get primaryKey => {id};
+}
+
+@TableIndex(name: 'title_index', columns: {#title})
+@TableIndex(name: 'body_index', columns: {#body})
+@TableIndex(name: 'message_id_index', columns: {#messageId})
+class Translations extends Table {
+ TextColumn get messageId => text()();
+
+ TextColumn get title => text()();
+
+ TextColumn get body => text()();
+
+ TextColumn get languagesCode => text()();
+
+ TextColumn get pdf => text().nullable()();
+
+ @override
+ Set get primaryKey => {messageId, languagesCode};
+}
+
+class Favorites extends Table {
+ TextColumn get id => text()();
+
+ @override
+ Set get primaryKey => {id};
+}
+
+@DriftDatabase(tables: [Messages, Translations, Favorites])
+class AppDatabase extends _$AppDatabase {
+ AppDatabase() : super(_createConnection());
+
+ static DatabaseConnection _createConnection() {
+ return DatabaseConnection.delayed(Future(() async {
+ final dbFolder = await getApplicationDocumentsDirectory();
+ final filePath = p.join(dbFolder.path, 'LGCC_Search/internal/db.sqlite');
+
+ final file = File(filePath);
+ final database = NativeDatabase(file,
+ logStatements: false, cachePreparedStatements: true);
+
+ return DatabaseConnection(database);
+ }));
+ }
+
+ @override
+ int get schemaVersion => 1;
+
+ Future addMessages(List drafts) async {
+ if (drafts.isEmpty) {
+ return; // No hay nada que insertar
+ }
+
+ try {
+ // Preparar las listas para mensajes y traducciones
+ List messagesList = [];
+ List translationsList = [];
+
+ // Validar y preparar los datos
+ for (final draft in drafts) {
+ // Validar que el ID no esté vacío
+ if (draft.id.isEmpty) {
+ if (kDebugMode) {
+ print('Saltando mensaje con ID vacío');
+ }
+ continue;
+ }
+
+ // Preparar el mensaje
+ messagesList.add(MessagesCompanion(
+ id: Value(draft.id),
+ country: Value(draft.country),
+ city: Value(draft.city),
+ date: Value(draft.date),
+ activity: Value(draft.activity),
+ thumbnail: Value(draft.thumbnail),
+ draft: Value(draft.draft),
+ ));
+
+ // Preparar la traducción
+ translationsList.add(TranslationsCompanion(
+ messageId: Value(draft.id),
+ body: Value(draft.body ?? ''),
+ languagesCode: Value(draft.languagesCode),
+ title: Value(draft.title),
+ pdf: Value(draft.pdf ?? ''),
+ ));
+ }
+
+ // Ejecutar las inserciones en una transacción para garantizar la atomicidad
+ await transaction(() async {
+ // Insertar mensajes con manejo de conflictos
+ for (var message in messagesList) {
+ await into(messages).insert(
+ message,
+ onConflict: DoUpdate(
+ (old) => MessagesCompanion(
+ country: message.country,
+ city: message.city,
+ date: message.date,
+ activity: message.activity,
+ thumbnail: message.thumbnail,
+ draft: message.draft,
+ ),
+ target: [messages.id],
+ ),
+ );
+ }
+
+ // Insertar traducciones con manejo de conflictos
+ for (var translation in translationsList) {
+ await into(translations).insert(
+ translation,
+ onConflict: DoUpdate(
+ (old) => TranslationsCompanion(
+ title: translation.title,
+ body: translation.body,
+ pdf: translation.pdf,
+ ),
+ target: [translations.messageId, translations.languagesCode],
+ ),
+ );
+ }
+ });
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error al insertar mensajes en la base de datos: $e');
+ }
+ // Reintento con enfoque más seguro en caso de error
+ try {
+ // Insertar uno por uno para identificar registros problemáticos
+ for (int i = 0; i < drafts.length; i++) {
+ final draft = drafts[i];
+ try {
+ // Insertar mensaje
+ await into(messages).insert(
+ MessagesCompanion(
+ id: Value(draft.id),
+ country: Value(draft.country),
+ city: Value(draft.city),
+ date: Value(draft.date),
+ activity: Value(draft.activity),
+ thumbnail: Value(draft.thumbnail),
+ draft: Value(draft.draft),
+ ),
+ onConflict: DoUpdate(
+ (old) => MessagesCompanion(
+ country: Value(draft.country),
+ city: Value(draft.city),
+ date: Value(draft.date),
+ activity: Value(draft.activity),
+ thumbnail: Value(draft.thumbnail),
+ draft: Value(draft.draft),
+ ),
+ target: [messages.id],
+ ),
+ );
+
+ // Insertar traducción
+ await into(translations).insert(
+ TranslationsCompanion(
+ messageId: Value(draft.id),
+ body: Value(draft.body ?? ''),
+ languagesCode: Value(draft.languagesCode),
+ title: Value(draft.title),
+ pdf: Value(draft.pdf ?? ''),
+ ),
+ onConflict: DoUpdate(
+ (old) => TranslationsCompanion(
+ title: Value(draft.title),
+ body: Value(draft.body ?? ''),
+ pdf: Value(draft.pdf ?? ''),
+ ),
+ target: [translations.messageId, translations.languagesCode],
+ ),
+ );
+ } catch (innerError) {
+ if (kDebugMode) {
+ print(
+ 'Error al insertar mensaje #$i (ID: ${draft.id}): $innerError');
+ }
+ // Continuar con el siguiente registro
+ }
+ }
+ } catch (fallbackError) {
+ if (kDebugMode) {
+ print('Error en el proceso de recuperación: $fallbackError');
+ }
+ }
+ }
+ }
+
+ Future>> getPdfList() async {
+ final locale = await ConfigService.getLocale();
+ try {
+ final queryResult = await customSelect(
+ "SELECT pdf, id, t.title, date, m.country, activity FROM messages LEFT JOIN translations ON messages.id = translations.message_id WHERE pdf <> '' AND locale = '$locale'",
+ readsFrom: {messages}).map((row) {
+ return {
+ 'pdf': row.read('pdf'),
+ 'title': row.read('title')
+ };
+ }).get();
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching PDF list: $e');
+ }
+ return [];
+ }
+ }
+
+ Future> getMessages(bool isDraft, [String? localeParam]) async {
+ final locale = localeParam ?? await ConfigService.getLocale();
+ if (locale.isEmpty) {
+ return [];
+ }
+
+ try {
+ // Consulta SQL optimizada sin usar INDEXED BY
+ final queryResult = await customSelect(
+ """
+ SELECT DISTINCT
+ m.id,
+ m.date,
+ m.activity,
+ m.thumbnail,
+ m.draft,
+ m.country,
+ m.city,
+ t.languages_code as locale,
+ t.title,
+ t.pdf,
+ t.languages_code
+ FROM
+ messages m
+ JOIN translations t ON m.id = t.message_id
+ WHERE
+ m.draft = ?
+ AND t.languages_code = ?
+ ORDER BY m.date DESC
+ LIMIT 20
+ """,
+ variables: [
+ Variable(isDraft ? 1 : 0),
+ Variable.withString(locale),
+ ],
+ readsFrom: {messages, translations},
+ ).map((row) {
+ return Draft(
+ id: row.read('id'),
+ title: row.read('title'),
+ date: row.read('date'),
+ activity: row.read('activity'),
+ thumbnail: row.read('thumbnail'),
+ draft: row.read('draft'),
+ locale: row.read('locale'),
+ country: row.read('country'),
+ city: row.read('city'),
+ // Cargar el cuerpo solo cuando sea necesario para mejorar el rendimiento
+ body: '',
+ pdf: row.read('pdf') ?? '',
+ languagesCode: row.read('languages_code'),
+ );
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching messages: $e');
+ }
+ return [];
+ }
+ }
+
+ // Nuevo método para cargar el cuerpo de un mensaje específico cuando sea necesario
+ Future getMessageBody(String messageId, String languageCode) async {
+ try {
+ final result = await customSelect(
+ """
+ SELECT body
+ FROM translations
+ WHERE message_id = ? AND languages_code = ?
+ """,
+ variables: [
+ Variable.withString(messageId),
+ Variable.withString(languageCode),
+ ],
+ ).getSingleOrNull();
+
+ return result?.read('body') ?? '';
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching message body: $e');
+ }
+ return '';
+ }
+ }
+
+ Future> getYearActivities(String year, String month) async {
+ final locale = await ConfigService.getLocale();
+ try {
+ final queryResult = await customSelect(
+ "SELECT DISTINCT m.id, t.title, m.date, m.activity, m.thumbnail, m.draft, t.languages_code as locale, m.country, m.city, t.body, t.pdf, t.languages_code, strftime('%Y', datetime(m.date, 'unixepoch')) AS year, strftime('%m', datetime(m.date, 'unixepoch')) AS month "
+ "FROM messages m JOIN translations t ON m.id = t.message_id "
+ "WHERE strftime('%Y', datetime(m.date, 'unixepoch')) = ? AND strftime('%m', datetime(m.date, 'unixepoch')) = ? AND t.languages_code = ? "
+ "ORDER BY m.date DESC, m.activity DESC",
+ variables: [
+ Variable.withString(year),
+ Variable.withString(month),
+ Variable.withString(locale)
+ ],
+ readsFrom: {messages, translations},
+ ).map((row) {
+ final id = row.read('id');
+ final title = row.read('title');
+ final date = row.read('date');
+ final activity = row.read('activity');
+ final thumbnail = row.read('thumbnail');
+ final draft = row.read('draft');
+ final locale = row.read('locale');
+ final country = row.read('country');
+ final city = row.read('city');
+ final body = row.read('body') ?? '';
+ final pdf = row.read('pdf') ?? '';
+ final languagesCode = row.read('languages_code');
+ return Draft(
+ id: id,
+ title: title,
+ date: date,
+ activity: activity,
+ thumbnail: thumbnail,
+ draft: draft,
+ locale: locale,
+ country: country,
+ city: city,
+ body: body,
+ pdf: pdf,
+ languagesCode: languagesCode,
+ );
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching activities: $e');
+ }
+ return [];
+ }
+ }
+
+ Future> getMonths(String year) async {
+ try {
+ final queryResult = await customSelect(
+ "SELECT DISTINCT strftime('%m', datetime(date, 'unixepoch')) AS month FROM messages WHERE strftime('%Y', datetime(date, 'unixepoch')) = ? ORDER BY month ASC",
+ variables: [Variable.withString(year)],
+ readsFrom: {messages},
+ ).map((row) {
+ return row.read('month');
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching months: $e');
+ }
+ return [];
+ }
+ }
+
+ Future toggleFavorite(String id) async {
+ final result = await customSelect(
+ 'SELECT COUNT(*) AS count FROM favorites WHERE id = ?',
+ variables: [Variable.withString(id)],
+ ).getSingle();
+
+ final isFavorite = result.read('count') > 0;
+
+ if (isFavorite) {
+ delete(favorites).delete(FavoritesCompanion(id: Value(id)));
+ } else {
+ into(favorites).insert(
+ FavoritesCompanion(id: Value(id)),
+ );
+ }
+ }
+
+ Future checkFavorite(String id) async {
+ final result = await customSelect(
+ 'SELECT DISTINCT COUNT(*) AS count FROM favorites WHERE id = ?',
+ variables: [Variable.withString(id)],
+ ).getSingle();
+ final isFavorite = result.read('count') > 0;
+ return isFavorite;
+ }
+
+ Future> getFavorites() async {
+ final result = await customSelect('SELECT DISTINCT id FROM favorites')
+ .map((row) => row.read('id'))
+ .get();
+ return result;
+ }
+
+ // Método para obtener los años disponibles en la base de datos
+ Future> getAvailableYears() async {
+ try {
+ final queryResult = await customSelect(
+ "SELECT DISTINCT strftime('%Y', datetime(date, 'unixepoch')) AS year FROM messages ORDER BY year DESC",
+ readsFrom: {messages},
+ ).map((row) {
+ return row.read('year');
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching available years: $e');
+ }
+ return [];
+ }
+ }
+
+ // Método para obtener mensajes filtrados por año y mes
+ Future> getFilteredMessages(String year, String month,
+ [String? localeParam]) async {
+ final locale = localeParam ?? await ConfigService.getLocale();
+ if (locale.isEmpty) {
+ return [];
+ }
+
+ try {
+ final queryResult = await customSelect(
+ """
+ SELECT DISTINCT
+ m.id,
+ m.date,
+ m.activity,
+ m.thumbnail,
+ m.draft,
+ m.country,
+ m.city,
+ t.languages_code as locale,
+ t.title,
+ t.pdf,
+ t.languages_code
+ FROM
+ messages m
+ JOIN translations t ON m.id = t.message_id
+ WHERE
+ strftime('%Y', datetime(m.date, 'unixepoch')) = ?
+ AND strftime('%m', datetime(m.date, 'unixepoch')) = ?
+ AND t.languages_code = ?
+ ORDER BY m.date DESC
+ """,
+ variables: [
+ Variable.withString(year),
+ Variable.withString(month),
+ Variable.withString(locale),
+ ],
+ readsFrom: {messages, translations},
+ ).map((row) {
+ return Draft(
+ id: row.read('id'),
+ title: row.read('title'),
+ date: row.read('date'),
+ activity: row.read('activity'),
+ thumbnail: row.read('thumbnail'),
+ draft: row.read('draft'),
+ locale: row.read('locale'),
+ country: row.read('country'),
+ city: row.read('city'),
+ body: '',
+ pdf: row.read('pdf') ?? '',
+ languagesCode: row.read('languages_code'),
+ );
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching filtered messages: $e');
+ }
+ return [];
+ }
+ }
+
+ // Método para obtener todos los mensajes (borradores y no borradores)
+ Future> getAllMessages([String? localeParam, int? limit]) async {
+ final locale = localeParam ?? await ConfigService.getLocale();
+ if (locale.isEmpty) {
+ return [];
+ }
+
+ try {
+ // Consulta SQL optimizada para obtener todos los mensajes
+ var query = """
+ SELECT DISTINCT
+ m.id,
+ m.date,
+ m.activity,
+ m.thumbnail,
+ m.draft,
+ m.country,
+ m.city,
+ t.languages_code as locale,
+ t.title,
+ t.body,
+ t.pdf,
+ t.languages_code
+ FROM
+ messages m
+ JOIN translations t ON m.id = t.message_id
+ WHERE
+ t.languages_code = ?
+ ORDER BY m.date DESC
+ """;
+
+ // Añadir límite si se especifica
+ if (limit != null) {
+ query += " LIMIT $limit";
+ }
+
+ final queryResult = await customSelect(
+ query,
+ variables: [
+ Variable.withString(locale),
+ ],
+ readsFrom: {messages, translations},
+ ).map((row) {
+ return Draft(
+ id: row.read('id'),
+ title: row.read('title'),
+ date: row.read('date'),
+ activity: row.read('activity'),
+ thumbnail: row.read('thumbnail'),
+ draft: row.read('draft'),
+ locale: row.read('locale'),
+ country: row.read('country'),
+ city: row.read('city'),
+ body: row.read('body') ?? '',
+ pdf: row.read('pdf') ?? '',
+ languagesCode: row.read('languages_code'),
+ );
+ }).get();
+
+ return queryResult;
+ } catch (e) {
+ if (kDebugMode) {
+ print('Error fetching all messages: $e');
+ }
+ return [];
+ }
+ }
+
+ Future> searchMessages(String query, String languagesCode) async {
+ try {
+ final mimirService = MimirService();
+ await mimirService.initialize();
+
+ // Buscar en Mimir para obtener los IDs relevantes y metadatos
+ final searchResult = await mimirService.search(query, languagesCode);
+
+ // Extraer IDs de los resultados
+ List